Compare commits

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

* Screen api in onboarding flow

* PnlChart rounding

* UnauthorizedDialog

* Add RateLimitDialog

* Update dialog open logic

* nits

* Screen before Transfer

* Validate network, remove input autofill color

* Use screenAddresses from dydxClient

* Check recipientAddresses

* lock-file

* Change Wallet Restricted Dialog to v4-blocked message

* nits

* Unused param

* consolidate environment validateFn

* Remove extra space, simplify WithdrawForm error msg

* Simply map logic, rearrange priority of screened messages

* WalletRestricted -> RestrictedWallet

* Update restriction modal title and add something went wrong

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

* remove WC 1

* gitignore .env

* remove .env

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

* udpate wallet connect2 metadata
2023-10-12 17:10:51 -04:00
41 changed files with 1069 additions and 685 deletions
+1 -1
View File
@@ -40,7 +40,7 @@
"@cosmjs/tendermint-rpc": "^0.31.0", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^0.6.4", "@dydxprotocol/v4-abacus": "^0.6.4",
"@dydxprotocol/v4-client-js": "^0.36.1", "@dydxprotocol/v4-client-js": "^0.36.1",
"@dydxprotocol/v4-localization": "^0.1.24", "@dydxprotocol/v4-localization": "^0.1.30",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
+4 -4
View File
@@ -33,8 +33,8 @@ dependencies:
specifier: ^0.36.1 specifier: ^0.36.1
version: 0.36.1 version: 0.36.1
'@dydxprotocol/v4-localization': '@dydxprotocol/v4-localization':
specifier: ^0.1.24 specifier: ^0.1.30
version: 0.1.24 version: 0.1.30
'@ethersproject/providers': '@ethersproject/providers':
specifier: ^5.7.2 specifier: ^5.7.2
version: 5.7.2 version: 5.7.2
@@ -1010,8 +1010,8 @@ packages:
- utf-8-validate - utf-8-validate
dev: false dev: false
/@dydxprotocol/v4-localization@0.1.24: /@dydxprotocol/v4-localization@0.1.30:
resolution: {integrity: sha512-zSiu8+/6BWNWZXxe73zUp/UjuLG1Cp5UuhXKtbATqqkeZyVCs01QxbBCTs1MLfgsLh02IrfhXCPHvXnzZyReEw==} resolution: {integrity: sha512-xTlQuQmPz7wfGkjd07giYMAxRrolDbJE3DryJSIMB6R1MBq1wLzAcwp/iR/nELRwilRX9c44Tap0gGt+oAajiQ==}
dev: false dev: false
/@dydxprotocol/v4-proto@0.2.1: /@dydxprotocol/v4-proto@0.2.1:
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 122 KiB

+45 -19
View File
@@ -35,11 +35,13 @@
"name": "v4 Dev", "name": "v4 Dev",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -94,11 +96,13 @@
"name": "v4 Dev 2", "name": "v4 Dev 2",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -152,11 +156,13 @@
"name": "v4 Dev 4", "name": "v4 Dev 4",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -210,11 +216,13 @@
"name": "v4 Dev 5", "name": "v4 Dev 5",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -268,11 +276,13 @@
"name": "v4 Staging", "name": "v4 Staging",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -330,11 +340,13 @@
"name": "v4 Staging West", "name": "v4 Staging West",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -391,12 +403,14 @@
"dydxprotocol-testnet": { "dydxprotocol-testnet": {
"name": "v4 Public Testnet", "name": "v4 Public Testnet",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -455,12 +469,14 @@
"dydxprotocol-testnet-dydx": { "dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX", "name": "v4 Public Testnet/dYdX",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -518,12 +534,14 @@
"dydxprotocol-testnet-dsrv": { "dydxprotocol-testnet-dsrv": {
"name": "v4 Public Testnet/DSRV", "name": "v4 Public Testnet/DSRV",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -581,12 +599,14 @@
"dydxprotocol-testnet-nodefleet": { "dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet", "name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -644,12 +664,14 @@
"dydxprotocol-testnet-kingnodes": { "dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes", "name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -707,12 +729,14 @@
"dydxprotocol-testnet-liquify": { "dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify", "name": "v4 Public Testnet/Liquify",
"ethereumChainId": "5", "ethereumChainId": "5",
"dydxChainId": "dydx-testnet-3", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
@@ -771,12 +795,14 @@
"name": "v4", "name": "v4",
"ethereumChainId": "1", "ethereumChainId": "1",
"dydxChainId": "[mainnet chain id]", "dydxChainId": "[mainnet chain id]",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": true, "isMainNet": true,
"tokens": { "tokens": {
"comment": "Change according to mainnet release", "comment": "Change according to mainnet release",
"chain": { "chain": {
"name": "DYDX", "name": "DYDX",
"denom": "dv4tnt", "denom": "adv4tnt",
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
"usdc": { "usdc": {
+439 -449
View File
@@ -1,474 +1,464 @@
[ [
{ {
"id":"c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96", "id": "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
"name":"MetaMask", "name": "MetaMask",
"description":"", "description": "",
"homepage":"https://metamask.io/", "homepage": "https://metamask.io/",
"chains":[ "chains": [
"eip155:1", "eip155:1",
"eip155:42161", "eip155:42161",
"eip155:43114" "eip155:43114"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/us/app/metamask/id1438144202", "ios": "https://apps.apple.com/us/app/metamask/id1438144202",
"android":"https://play.google.com/store/apps/details?id=io.metamask", "android": "https://play.google.com/store/apps/details?id=io.metamask",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"metamask:", "native": "metamask:",
"universal":"https://metamask.app.link" "universal": "https://metamask.app.link"
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"MetaMask", "shortName": "MetaMask",
"colors":{ "colors": {
"primary":"rgb(255, 255, 255)", "primary": "rgb(255, 255, 255)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "MetaMask",
"iosMinVersion": "0",
"imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"connections": [
{
"type": "walletConnectV2",
"native": "metamask:",
"universal": "https://metamask.app.link"
} }
}, ],
"config":{ "methods": [
"comment":"MetaMask", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/metamask.png", "eth_signTypedData",
"image":"metamask.png", "wallet_addEthereumChain"
"encoding":"=\"#%/<>?@\\^`{|}:&", ]
"connections":[ },
{ "userFields": {
"type":"walletConnectV2", "analyticEvent": "METAMASK",
"native":"metamask:", "imageName": "metamask.png"
"universal":"https://metamask.app.link" }
}
]
},
"userFields":{
"analyticEvent":"METAMASK"
}
}, },
{ {
"id":"coinbase-wallet", "id": "coinbase-wallet",
"name":"Coinbase Wallet", "name": "Coinbase Wallet",
"description":"", "description": "",
"homepage":"https://www.coinbase.com/wallet", "homepage": "https://www.coinbase.com/wallet",
"chains":[ "chains": [
"eip155:1" "eip155:1"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/us/app/coinbase-wallet-nfts-crypto/id1278383455", "ios": "https://apps.apple.com/us/app/coinbase-wallet-nfts-crypto/id1278383455",
"android":"https://play.google.com/store/apps/details?id=org.toshi", "android": "https://play.google.com/store/apps/details?id=org.toshi",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"cbwallet:", "native": "cbwallet:",
"universal":"" "universal": ""
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"Coinbase", "shortName": "Coinbase",
"colors":{ "colors": {
"primary":"rgb(255, 255, 255)", "primary": "rgb(255, 255, 255)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "Coinbase",
"iosMinVersion": "1.8.0",
"imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"connections": [
{
"type": "walletSegue",
"native": "cbwallet:",
"universal": ""
} }
}, ],
"config":{ "methods": [
"comment":"Coinbase", "eth_sendTransaction",
"iosMinVersion":"1.8.0", "personal_sign",
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/coinbase-wallet.png", "eth_signTypedData",
"image":"coinbase-wallet.png", "wallet_addEthereumChain"
"encoding":"=\"#%/<>?@\\^`{|}:&", ]
"connections":[ },
{ "userFields": {
"type":"walletSegue", "analyticEvent": "COINBASE_WALLET",
"native":"cbwallet:", "imageName": "coinbase-wallet.png"
"universal":"" }
}
]
},
"userFields":{
"analyticEvent":"COINBASE_WALLET"
}
}, },
{ {
"id":"9d373b43ad4d2cf190fb1a774ec964a1addf406d6fd24af94ab7596e58c291b2", "id": "9d373b43ad4d2cf190fb1a774ec964a1addf406d6fd24af94ab7596e58c291b2",
"name":"imToken", "name": "imToken",
"description":"", "description": "",
"homepage":"https://token.im/", "homepage": "https://token.im/",
"chains":[ "chains": [
"eip155:1" "eip155:1"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://itunes.apple.com/us/app/imtoken2/id1384798940", "ios": "https://itunes.apple.com/us/app/imtoken2/id1384798940",
"android":"https://play.google.com/store/apps/details?id=im.token.app", "android": "https://play.google.com/store/apps/details?id=im.token.app",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"imtokenv2:", "native": "imtokenv2:",
"universal":"" "universal": ""
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"imToken", "shortName": "imToken",
"colors":{ "colors": {
"primary":"rgb(255, 255, 255)", "primary": "rgb(255, 255, 255)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "imToken",
"iosMinVersion": "0",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "imtokenv2:",
"universal": ""
} }
}, ],
"config":{ "methods": [
"comment":"imToken", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/imtoken.png", "eth_signTypedData"
"image":"imtoken.png", ]
"connections":[ },
{ "userFields": {
"type":"walletConnect", "analyticEvent": "IMTOKEN",
"native":"imtokenv2:", "imageName": "imtoken.png"
"universal":"" }
}
]
},
"userFields":{
"analyticEvent":"IMTOKEN"
}
}, },
{ {
"id":"4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0", "id": "4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0",
"name":"Trust Wallet", "name": "Trust Wallet",
"description":"", "description": "",
"homepage":"https://trustwallet.com/", "homepage": "https://trustwallet.com/",
"chains":[ "chains": [
"eip155:1", "eip155:1",
"eip155:42161", "eip155:42161",
"eip155:43114" "eip155:43114"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/app/apple-store/id1288339409", "ios": "https://apps.apple.com/app/apple-store/id1288339409",
"android":"https://play.google.com/store/apps/details?id=com.wallet.crypto.trustapp", "android": "https://play.google.com/store/apps/details?id=com.wallet.crypto.trustapp",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"trust:", "native": "trust:",
"universal":"https://link.trustwallet.com" "universal": "https://link.trustwallet.com"
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"Trust", "shortName": "Trust",
"colors":{ "colors": {
"primary":"rgb(51, 117, 187)", "primary": "rgb(51, 117, 187)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "Trust Wallet",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "trust:",
"universal": "https://link.trustwallet.com"
} }
}, ],
"config":{ "methods": [
"comment":"Trust Wallet", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"encoding":"=\"#%/<>?@\\^`{|}:&", "eth_signTypedData"
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/trust-wallet.png", ]
"image":"trust-wallet.png", },
"connections":[ "userFields": {
{ "analyticEvent": "TRUST_WALLET",
"type":"walletConnectV2", "imageName": "trust-wallet.png"
"native":"trust:", }
"universal":"https://link.trustwallet.com"
}
]
},
"userFields":{
"analyticEvent":"TRUST_WALLET"
}
}, },
{ {
"id":"Zerion", "id": "Zerion",
"name":"Zerion", "name": "Zerion",
"description":"", "description": "",
"homepage":"https://zerion.io/", "homepage": "https://zerion.io/",
"chains":[ "chains": [
"eip155:1" "eip155:1"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/app/apple-store/id1456732565", "ios": "https://apps.apple.com/app/apple-store/id1456732565",
"android":"https://play.google.com/store/apps/details?id=io.zerion.android", "android": "https://play.google.com/store/apps/details?id=io.zerion.android",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"zerion:", "native": "zerion:",
"universal":"" "universal": ""
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"Zerion", "shortName": "Zerion",
"colors":{ "colors": {
"primary":"rgb(51, 117, 187)", "primary": "rgb(51, 117, 187)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "Zerion Wallet",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "zerion:",
"universal": ""
} }
}, ],
"config":{ "methods": [
"comment":"Zerion Wallet", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"encoding":"=\"#%/<>?@\\^`{|}:&", "eth_signTypedData"
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/zerion.png", ]
"image":"zerion.png", },
"connections":[ "userFields": {
{ "analyticEvent": "ZERION",
"type":"walletConnectV2", "imageName": "zerion.png"
"native":"zerion:", }
"universal":""
}
]
},
"userFields":{
"analyticEvent":"ZERION"
}
}, },
{ {
"id":"20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66", "id": "20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66",
"name":"TokenPocket", "name": "TokenPocket",
"description":"", "description": "",
"homepage":"https://tokenpocket.pro/", "homepage": "https://tokenpocket.pro/",
"chains":[ "chains": [
"eip155:1" "eip155:1"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/us/app/tokenpocket-trusted-wallet/id1436028697", "ios": "https://apps.apple.com/us/app/tokenpocket-trusted-wallet/id1436028697",
"android":"https://play.google.com/store/apps/details?id=vip.mytokenpocket", "android": "https://play.google.com/store/apps/details?id=vip.mytokenpocket",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"tpoutside:", "native": "tpoutside:",
"universal":"" "universal": ""
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"TokenPocket", "shortName": "TokenPocket",
"colors":{ "colors": {
"primary":"rgb(41, 128, 254)", "primary": "rgb(41, 128, 254)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "TokenPocket",
"iosMinVersion": "0",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "tpoutside:",
"universal": ""
} }
}, ],
"config":{ "methods": [
"comment":"TokenPocket", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/tokenpocket.png", "eth_signTypedData"
"image":"tokenpocket.png", ]
"connections":[ },
{ "userFields": {
"type":"walletConnect", "analyticEvent": "TOKEN_POCKET",
"native":"tpoutside:", "imageName": "tokenpocket.png"
"universal":"" }
}
]
},
"userFields":{
"analyticEvent":"TOKEN_POCKET"
}
}, },
{ {
"id":"1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369", "id": "1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369",
"name":"Rainbow", "name": "Rainbow",
"description":"", "description": "",
"homepage":"https://rainbow.me/", "homepage": "https://rainbow.me/",
"chains":[ "chains": [
"eip155:1" "eip155:1"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/us/app/rainbow-ethereum-wallet/id1457119021", "ios": "https://apps.apple.com/us/app/rainbow-ethereum-wallet/id1457119021",
"android":"", "android": "",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"rainbow:", "native": "rainbow:",
"universal":"https://rnbwapp.com" "universal": "https://rnbwapp.com"
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"Rainbow", "shortName": "Rainbow",
"colors":{ "colors": {
"primary":"rgb(0, 30, 89)", "primary": "rgb(0, 30, 89)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "Rainbow",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"backlinked": true,
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "rainbow:",
"universal": "https://rnbwapp.com"
} }
}, ],
"config":{ "methods": [
"comment":"Rainbow", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"encoding":"=\"#%/<>?@\\^`{|}:&", "eth_signTypedData"
"backlinked":true, ]
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/rainbow-wallet.png", },
"image":"rainbow-wallet.png", "userFields": {
"connections":[ "analyticEvent": "RAINBOW_WALLET",
{ "imageName": "rainbow-wallet.png"
"type":"walletConnectV2", }
"native":"rainbow:",
"universal":"https://rnbwapp.com"
}
]
},
"userFields":{
"analyticEvent":"RAINBOW_WALLET"
}
}, },
{ {
"id":"09102e7bbbd3f92001eda104abe23803181629f695e8f1b95af96d88ff7d5890", "id": "09102e7bbbd3f92001eda104abe23803181629f695e8f1b95af96d88ff7d5890",
"name":"1inch Wallet", "name": "1inch Wallet",
"description":"", "description": "",
"homepage":"https://1inch.io/wallet/", "homepage": "https://1inch.io/wallet/",
"chains":[ "chains": [
"eip155:1", "eip155:1",
"eip155:56" "eip155:56"
], ],
"versions":[ "versions": [
"1" "1"
], ],
"app":{ "app": {
"browser":"", "browser": "",
"ios":"https://apps.apple.com/us/app/1inch-defi-wallet/id1546049391", "ios": "https://apps.apple.com/us/app/1inch-defi-wallet/id1546049391",
"android":"", "android": "",
"mac":"", "mac": "",
"windows":"", "windows": "",
"linux":"" "linux": ""
}, },
"mobile":{ "mobile": {
"native":"oneinch:", "native": "oneinch:",
"universal":"https://wallet.1inch.io" "universal": "https://wallet.1inch.io"
}, },
"desktop":{ "desktop": {
"native":"", "native": "",
"universal":"" "universal": ""
}, },
"metadata":{ "metadata": {
"shortName":"1inch", "shortName": "1inch",
"colors":{ "colors": {
"primary":"rgb(31, 36, 46)", "primary": "rgb(31, 36, 46)",
"secondary":"" "secondary": ""
}
},
"config": {
"comment": "1inch",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "oneinch:",
"universal": "https://wallet.1inch.io"
} }
}, ],
"config":{ "methods": [
"comment":"1inch", "eth_sendTransaction",
"iosMinVersion":"0", "personal_sign",
"encoding":"=\"#%/<>?@\\^`{|}:&", "eth_signTypedData"
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/1inch.png", ]
"image":"1inch.png", },
"connections":[ "userFields": {
{ "analyticEvent": "1INCH",
"type":"walletConnect", "imageName": "1inch.png"
"native":"oneinch:", }
"universal":"https://wallet.1inch.io"
}
]
},
"userFields":{
"analyticEvent":"1INCH"
}
},
{
"id":"bae74827272509a6d63ea25514d9c68ad235c14e45e183518c7ded4572a1b0c4",
"name":"Huobi Wallet",
"description":"",
"homepage":"https://huobiwallet.com/",
"chains":[
"eip155:1"
],
"versions":[
"1"
],
"app":{
"browser":"",
"ios":"https://apps.apple.com/us/app/id1433883012",
"android":"https://play.google.com/store/apps/details?id=com.huobionchainwallet.gp",
"mac":"",
"windows":"",
"linux":""
},
"mobile":{
"native":"huobiwallet:",
"universal":""
},
"desktop":{
"native":"",
"universal":""
},
"metadata":{
"shortName":"Huobi",
"colors":{
"primary":"rgb(45,101,248)",
"secondary":""
}
},
"config":{
"comment":"Huobi",
"iosMinVersion":"0",
"encoding":"=\"#%/<>?@\\^`{|}:&",
"imageUrl":"https://v4.testnet.dydx.exchange/wallets/huobi-wallet.png",
"image":"huobi-wallet.png",
"connections":[
{
"type":"walletConnect",
"native":"huobiwallet:",
"universal":""
}
]
},
"userFields":{
"analyticEvent":"HUOBI_WALLET"
}
} }
] ]
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+4
View File
@@ -14,6 +14,7 @@ import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications'; import { NotificationsProvider } from '@/hooks/useNotifications';
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications'; import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount'; import { SubaccountProvider } from '@/hooks/useSubaccount';
import { SquidProvider } from '@/hooks/useSquid'; import { SquidProvider } from '@/hooks/useSquid';
@@ -66,6 +67,7 @@ const Content = () => {
<Route path=":market" element={<TradePage />} /> <Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} /> <Route path={AppRoute.Trade} element={<TradePage />} />
</Route> </Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} /> <Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={AppRoute.Rewards} element={<RewardsPage />} /> <Route path={AppRoute.Rewards} element={<RewardsPage />} />
{isTablet && ( {isTablet && (
@@ -79,6 +81,7 @@ const Content = () => {
<Route element={<GuardedMobileRoute />}> <Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} /> <Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route> </Route>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} /> <Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes> </Routes>
</Suspense> </Suspense>
@@ -109,6 +112,7 @@ const providers = [
wrapProvider(GrazProvider), wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }), wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider), wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider), wrapProvider(DydxProvider),
wrapProvider(AccountsProvider), wrapProvider(AccountsProvider),
wrapProvider(SubaccountProvider), wrapProvider(SubaccountProvider),
+10
View File
@@ -207,6 +207,16 @@ const InputStyle = css`
color: var(--color-text-0); color: var(--color-text-0);
opacity: 1; 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)` Styled.NumericFormat = styled(NumericFormat)`
+11
View File
@@ -78,8 +78,13 @@ export const Changes = Abacus.exchange.dydx.abacus.state.changes.Changes;
export type PerpetualStateChanges = Abacus.exchange.dydx.abacus.state.changes.StateChanges; export type PerpetualStateChanges = Abacus.exchange.dydx.abacus.state.changes.StateChanges;
export const AsyncAbacusStateManager = export const AsyncAbacusStateManager =
Abacus.exchange.dydx.abacus.state.manager.AsyncAbacusStateManager; Abacus.exchange.dydx.abacus.state.manager.AsyncAbacusStateManager;
// ------ Parsing Errors ------ //
export type ParsingError = Abacus.exchange.dydx.abacus.responses.ParsingError; export type ParsingError = Abacus.exchange.dydx.abacus.responses.ParsingError;
export type ParsingErrors = kollections.List<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 ------ // // ------ Perpetuals/Markets ------ //
export type PerpetualState = Abacus.exchange.dydx.abacus.output.PerpetualState; export type PerpetualState = Abacus.exchange.dydx.abacus.output.PerpetualState;
@@ -199,6 +204,12 @@ export type RiskLevels = (typeof riskLevels)[number];
// ------ Notifications ------ // // ------ Notifications ------ //
export type AbacusNotification = Abacus.exchange.dydx.abacus.output.Notification; 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 ------ // // ------ Enum Conversions ------ //
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? A ? A
+3
View File
@@ -10,7 +10,10 @@ export enum DialogTypes {
MobileSignIn = 'MobileSignIn', MobileSignIn = 'MobileSignIn',
Onboarding = 'Onboarding', Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails', OrderDetails = 'OrderDetails',
RateLimit = 'RateLimit',
Receive = 'Receive', Receive = 'Receive',
RestrictedGeo = 'RestrictedGeo',
RestrictedWallet = 'RestrictedWallet',
Trade = 'Trade', Trade = 'Trade',
Transfer = 'Transfer', Transfer = 'Transfer',
Withdraw = 'Withdraw', Withdraw = 'Withdraw',
+2
View File
@@ -16,6 +16,7 @@ import { useLocalStorage } from './useLocalStorage';
import { useNow } from './useNow'; import { useNow } from './useNow';
import { useOnClickOutside } from './useOnClickOutside'; import { useOnClickOutside } from './useOnClickOutside';
import { usePageTitlePriceUpdates } from './usePageTitlePriceUpdates'; import { usePageTitlePriceUpdates } from './usePageTitlePriceUpdates';
import { useRestrictions } from './useRestrictions';
import { useShouldShowFooter } from './useShouldShowFooter'; import { useShouldShowFooter } from './useShouldShowFooter';
import { useSelectedNetwork } from './useSelectedNetwork'; import { useSelectedNetwork } from './useSelectedNetwork';
import { useStringGetter } from './useStringGetter'; import { useStringGetter } from './useStringGetter';
@@ -40,6 +41,7 @@ export {
useNow, useNow,
useOnClickOutside, useOnClickOutside,
usePageTitlePriceUpdates, usePageTitlePriceUpdates,
useRestrictions,
useShouldShowFooter, useShouldShowFooter,
useSelectedNetwork, useSelectedNetwork,
useStringGetter, useStringGetter,
+23 -5
View File
@@ -5,18 +5,20 @@ import { AES, enc } from 'crypto-js';
import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js'; import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js';
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account'; import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage'; import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets'; import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
import { setOnboardingState, setOnboardingGuard } from '@/state/account'; import { setOnboardingState, setOnboardingGuard } from '@/state/account';
import { forceOpenDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import { log } from '@/lib/telemetry'; import { log } from '@/lib/telemetry';
import { useLocalStorage } from './useLocalStorage';
import { useWalletConnection } from './useWalletConnection';
import { useDydxClient } from './useDydxClient'; import { useDydxClient } from './useDydxClient';
import { useLocalStorage } from './useLocalStorage';
import { useRestrictions } from './useRestrictions';
import { useWalletConnection } from './useWalletConnection';
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined); const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
@@ -57,7 +59,9 @@ const useAccountsContext = () => {
forgetEvmSignature(previousEvmAddress); forgetEvmSignature(previousEvmAddress);
} }
if (evmAddress) abacusStateManager.setTransfersSourceAddress(evmAddress); if (evmAddress) {
abacusStateManager.setTransfersSourceAddress(evmAddress);
}
setPreviousEvmAddress(evmAddress); setPreviousEvmAddress(evmAddress);
}, [evmAddress]); }, [evmAddress]);
@@ -217,7 +221,6 @@ const useAccountsContext = () => {
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]); }, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
// abacus // abacus
// TODO: useAbacus({ dydxAddress })
useEffect(() => { useEffect(() => {
if (dydxAddress) abacusStateManager.setAccount(localDydxWallet); if (dydxAddress) abacusStateManager.setAccount(localDydxWallet);
else abacusStateManager.attemptDisconnectAccount(); else abacusStateManager.attemptDisconnectAccount();
@@ -258,6 +261,21 @@ const useAccountsContext = () => {
); );
}, [dydxSubaccounts]); }, [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 // Disconnect wallet / accounts
const disconnectLocalDydxWallet = () => { const disconnectLocalDydxWallet = () => {
setLocalDydxWallet(undefined); setLocalDydxWallet(undefined);
+25 -1
View File
@@ -17,8 +17,11 @@ import type { NetworkConfig, ConnectNetworkEvent } from '@/constants/abacus';
import { type Candle, RESOLUTION_MAP } from '@/constants/candles'; import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { log } from '@/lib/telemetry'; import { log } from '@/lib/telemetry';
import { useRestrictions } from './useRestrictions';
type DydxContextType = ReturnType<typeof useDydxClientContext>; type DydxContextType = ReturnType<typeof useDydxClientContext>;
const DydxContext = createContext<DydxContextType>({} as DydxContextType); const DydxContext = createContext<DydxContextType>({} as DydxContextType);
DydxContext.displayName = 'dYdXClient'; DydxContext.displayName = 'dYdXClient';
@@ -178,7 +181,27 @@ const useDydxClientContext = () => {
[requestCandles] [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 { return {
// Client initialization // Client initialization
@@ -192,5 +215,6 @@ const useDydxClientContext = () => {
// Public Methods // Public Methods
getCandlesForDatafeed, getCandlesForDatafeed,
screenAddresses,
}; };
}; };
+2
View File
@@ -10,6 +10,7 @@ import { useLocalStorage } from '@/hooks';
import { initializeLocalization } from '@/state/app'; import { initializeLocalization } from '@/state/app';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export const useInitializePage = () => { export const useInitializePage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -18,6 +19,7 @@ export const useInitializePage = () => {
const [localStorageNetwork] = useLocalStorage<DydxNetwork>({ const [localStorageNetwork] = useLocalStorage<DydxNetwork>({
key: LocalStorageKey.SelectedNetwork, key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT, defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: validateAgainstAvailableEnvironments,
}); });
useEffect(() => { useEffect(() => {
+53
View File
@@ -0,0 +1,53 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { RestrictionType } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { getRestrictionType } from '@/state/accountSelectors';
import { forceOpenDialog } from '@/state/dialogs';
import { isTruthy } from '@/lib/isTruthy';
const useRestrictionContext = () => {
const dispatch = useDispatch();
const restrictionType = useSelector(getRestrictionType, shallowEqual);
const [sanctionedAddresses, setSanctionedAddresses] = useState<string[]>([]);
useEffect(() => {
if (restrictionType === RestrictionType.GEO_RESTRICTED) {
dispatch(
forceOpenDialog({ type: DialogTypes.RestrictedGeo, dialogProps: { preventClose: true } })
);
}
}, [restrictionType, dispatch]);
const updateSanctionedAddresses = useCallback(
(screenedAddresses: { [address: string]: boolean }) => {
const toAdd = Object.entries(screenedAddresses)
.filter(([, isSanctioned]) => isSanctioned)
.map(([address]) => address);
if (toAdd.length) {
setSanctionedAddresses([...sanctionedAddresses, ...toAdd]);
}
},
[sanctionedAddresses]
);
return {
isBadActor: restrictionType === RestrictionType.USER_RESTRICTED,
isGeoRestricted: restrictionType === RestrictionType.GEO_RESTRICTED,
updateSanctionedAddresses,
sanctionedAddresses: useMemo(() => new Set(sanctionedAddresses), [sanctionedAddresses]),
};
};
type RestrictionContextType = ReturnType<typeof useRestrictionContext>;
const RestrictionContext = createContext<RestrictionContextType>({} as RestrictionContextType);
RestrictionContext.displayName = 'Restriction';
export const RestrictionProvider = ({ ...props }) => (
<RestrictionContext.Provider value={useRestrictionContext()} {...props} />
);
export const useRestrictions = () => useContext(RestrictionContext);
+18 -2
View File
@@ -1,13 +1,17 @@
import { useCallback } from 'react'; import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork } from '@/constants/networks'; import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useAccounts, useLocalStorage } from '@/hooks'; import { useAccounts, useLocalStorage } from '@/hooks';
import { setSelectedNetwork } from '@/state/app'; import { setSelectedNetwork } from '@/state/app';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { openDialog } from '@/state/dialogs';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export const useSelectedNetwork = (): { export const useSelectedNetwork = (): {
switchNetwork: (network: DydxNetwork) => void; switchNetwork: (network: DydxNetwork) => void;
@@ -20,8 +24,20 @@ export const useSelectedNetwork = (): {
const [, setLocalStorageNetwork] = useLocalStorage<DydxNetwork>({ const [, setLocalStorageNetwork] = useLocalStorage<DydxNetwork>({
key: LocalStorageKey.SelectedNetwork, key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT, defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: validateAgainstAvailableEnvironments,
}); });
useEffect(() => {
if (
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-3'
) {
dispatch(
openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } })
);
}
}, [selectedNetwork]);
const switchNetwork = useCallback( const switchNetwork = useCallback(
(network: DydxNetwork) => { (network: DydxNetwork) => {
disconnect(); disconnect();
+1 -1
View File
@@ -36,7 +36,6 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient'; import { useDydxClient } from './useDydxClient';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>; type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType); const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
SubaccountContext.displayName = 'Subaccount'; SubaccountContext.displayName = 'Subaccount';
@@ -275,6 +274,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
await getFaucetFunds({ dydxAddress, subaccountNumber }); await getFaucetFunds({ dydxAddress, subaccountNumber });
} catch (error) { } catch (error) {
log('useSubaccount/getFaucetFunds', error); log('useSubaccount/getFaucetFunds', error);
throw error;
} }
}, [dydxAddress, getFaucetFunds, subaccountNumber]); }, [dydxAddress, getFaucetFunds, subaccountNumber]);
+6
View File
@@ -15,9 +15,12 @@ import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog'; import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog'; import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog'; import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog'; import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
import { TradeDialog } from '@/views/dialogs/TradeDialog'; import { TradeDialog } from '@/views/dialogs/TradeDialog';
import { TransferDialog } from '@/views/dialogs/TransferDialog'; import { TransferDialog } from '@/views/dialogs/TransferDialog';
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog'; import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog'; import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
@@ -53,7 +56,10 @@ export const DialogManager = () => {
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />, [DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />, [DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />, [DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />, [DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
[DialogTypes.RestrictedWallet]: <RestrictedWalletDialog {...modalProps} />,
[DialogTypes.Trade]: <TradeDialog {...modalProps} />, [DialogTypes.Trade]: <TradeDialog {...modalProps} />,
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />, [DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />, [DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
+5
View File
@@ -47,6 +47,11 @@ class AbacusRest implements AbacusRestProtocol {
body, body,
}; };
if (!url) {
callback(null, 0);
return;
}
fetch(url, options) fetch(url, options)
.then(async (response) => { .then(async (response) => {
const data = await response.text(); const data = await response.text();
+5
View File
@@ -25,6 +25,7 @@ import {
setFundingPayments, setFundingPayments,
setHistoricalPnl, setHistoricalPnl,
setLatestOrder, setLatestOrder,
setRestrictionType,
setSubaccount, setSubaccount,
setTransfers, setTransfers,
setWallet, setWallet,
@@ -121,6 +122,10 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
); );
} }
if (changes.has(Changes.restriction)) {
dispatch(setRestrictionType(updatedState.restriction));
}
subaccountNumbers?.forEach((subaccountId: number) => { subaccountNumbers?.forEach((subaccountId: number) => {
if (subaccountId !== null) { if (subaccountId !== null) {
if (changes.has(Changes.subaccount)) { if (changes.has(Changes.subaccount)) {
+4
View File
@@ -0,0 +1,4 @@
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
+2 -3
View File
@@ -92,11 +92,10 @@ const walletconnect2ConnectorOptions: ConstructorParameters<typeof WalletConnect
options: { options: {
projectId: import.meta.env.VITE_WALLETCONNECT2_PROJECT_ID, projectId: import.meta.env.VITE_WALLETCONNECT2_PROJECT_ID,
metadata: { metadata: {
// TODO: update to local URLs/images
name: 'dYdX', name: 'dYdX',
description: '', description: '',
url: 'https://trade.dydx.exchange', url: import.meta.env.VITE_BASE_URL,
icons: ['https://trade.dydx.exchange/cbw-image.png'], icons: [`${import.meta.env.VITE_BASE_URL}/cbw-image.png}`],
}, },
showQrModal: true, showQrModal: true,
qrModalOptions: { qrModalOptions: {
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components'; import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import { TooltipContextType } from '@visx/xychart'; import { TooltipContextType } from '@visx/xychart';
import BigNumber from 'bignumber.js';
import type { Nullable } from '@/constants/abacus'; import type { Nullable } from '@/constants/abacus';
import { OnboardingState } from '@/constants/account'; import { OnboardingState } from '@/constants/account';
@@ -25,7 +26,6 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
import { isTruthy } from '@/lib/isTruthy'; import { isTruthy } from '@/lib/isTruthy';
import { MustBigNumber } from '@/lib/numbers'; import { MustBigNumber } from '@/lib/numbers';
export const usePortfolioValues = ({ export const usePortfolioValues = ({
equity, equity,
visibleData, visibleData,
@@ -140,7 +140,12 @@ export const AccountDetailsAndHistory = () => {
<Styled.AccountValue> <Styled.AccountValue>
<Styled.WithLabel label={accountValueLabel}> <Styled.WithLabel label={accountValueLabel}>
<Styled.AccountEquity> <Styled.AccountEquity>
<Output type={OutputType.Fiat} value={accountEquity} withBaseFont /> <Output
type={OutputType.Fiat}
value={accountEquity}
roundingMode={BigNumber.ROUND_FLOOR}
withBaseFont
/>
</Styled.AccountEquity> </Styled.AccountEquity>
<Styled.PnlDiff isPositive={MustBigNumber(pnlDiff).gte(0)}> <Styled.PnlDiff isPositive={MustBigNumber(pnlDiff).gte(0)}>
{pnlDiff && <TriangleIndicator value={MustBigNumber(pnlDiff)} />} {pnlDiff && <TriangleIndicator value={MustBigNumber(pnlDiff)} />}
-4
View File
@@ -3,7 +3,6 @@ import { configureStore } from '@reduxjs/toolkit';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import appMiddleware from './appMiddleware'; import appMiddleware from './appMiddleware';
import localizationMiddleware from './localizationMiddleware'; import localizationMiddleware from './localizationMiddleware';
import routerMiddleware from './routerMiddleware';
import { accountSlice } from './account'; import { accountSlice } from './account';
import { appSlice } from './app'; import { appSlice } from './app';
@@ -13,7 +12,6 @@ import { dialogsSlice } from './dialogs';
import { inputsSlice } from './inputs'; import { inputsSlice } from './inputs';
import { layoutSlice } from './layout'; import { layoutSlice } from './layout';
import { localizationSlice } from './localization'; import { localizationSlice } from './localization';
import { navigationSlice } from './navigation';
import { perpetualsSlice } from './perpetuals'; import { perpetualsSlice } from './perpetuals';
export const commandMenuSlices = [layoutSlice, localizationSlice]; export const commandMenuSlices = [layoutSlice, localizationSlice];
@@ -28,7 +26,6 @@ export const store = configureStore({
inputs: inputsSlice.reducer, inputs: inputsSlice.reducer,
layout: layoutSlice.reducer, layout: layoutSlice.reducer,
localization: localizationSlice.reducer, localization: localizationSlice.reducer,
navigation: navigationSlice.reducer,
perpetuals: perpetualsSlice.reducer, perpetuals: perpetualsSlice.reducer,
}, },
@@ -38,7 +35,6 @@ export const store = configureStore({
}), }),
appMiddleware, appMiddleware,
localizationMiddleware, localizationMiddleware,
routerMiddleware,
], ],
devTools: process.env.NODE_ENV !== 'production', devTools: process.env.NODE_ENV !== 'production',
+40 -18
View File
@@ -12,6 +12,7 @@ import type {
SubaccountTransfers, SubaccountTransfers,
HistoricalPnlPeriods, HistoricalPnlPeriods,
SubAccountHistoricalPNLs, SubAccountHistoricalPNLs,
UsageRestriction,
} from '@/constants/abacus'; } from '@/constants/abacus';
import { OnboardingGuard, OnboardingState } from '@/constants/account'; import { OnboardingGuard, OnboardingState } from '@/constants/account';
@@ -21,34 +22,46 @@ import { WalletType } from '@/constants/wallets';
import { getLocalStorage } from '@/lib/localStorage'; import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = { export type AccountState = {
balances?: Record<string, AccountBalance>;
stakingBalances?: Record<string, AccountBalance>;
wallet?: Nullable<Wallet>;
walletType?: WalletType;
subaccount?: Nullable<Subaccount>;
fills?: SubaccountFills; fills?: SubaccountFills;
fundingPayments?: SubaccountFundingPayments; fundingPayments?: SubaccountFundingPayments;
transfers?: SubaccountTransfers; transfers?: SubaccountTransfers;
historicalPnl?: SubAccountHistoricalPNLs;
onboardingGuards: Record<OnboardingGuard, boolean | undefined>;
onboardingState: OnboardingState;
clearedOrderIds?: string[]; clearedOrderIds?: string[];
hasUnseenFillUpdates: boolean; hasUnseenFillUpdates: boolean;
hasUnseenOrderUpdates: boolean; hasUnseenOrderUpdates: boolean;
historicalPnl?: SubAccountHistoricalPNLs;
latestOrder?: Nullable<SubaccountOrder>; latestOrder?: Nullable<SubaccountOrder>;
onboardingGuards: Record<OnboardingGuard, boolean | undefined>;
onboardingState: OnboardingState;
subaccount?: Nullable<Subaccount>;
uncommittedOrderClientIds: number[];
wallet?: Nullable<Wallet>;
walletType?: WalletType;
historicalPnlPeriod?: HistoricalPnlPeriods; historicalPnlPeriod?: HistoricalPnlPeriods;
balances?: Record<string, AccountBalance>; uncommittedOrderClientIds: number[];
stakingBalances?: Record<string, AccountBalance>;
restriction?: Nullable<UsageRestriction>;
}; };
const initialState: AccountState = { const initialState: AccountState = {
// Wallet
balances: undefined,
wallet: undefined,
walletType: getLocalStorage<WalletType>({
key: LocalStorageKey.OnboardingSelectedWalletType,
}),
// Subaccount
subaccount: undefined,
fills: undefined, fills: undefined,
fundingPayments: undefined, fundingPayments: undefined,
transfers: undefined, transfers: undefined,
clearedOrderIds: undefined,
hasUnseenFillUpdates: false,
hasUnseenOrderUpdates: false,
historicalPnl: undefined, historicalPnl: undefined,
latestOrder: undefined,
// Onboarding
onboardingGuards: { onboardingGuards: {
[OnboardingGuard.hasAcknowledgedTerms]: Boolean( [OnboardingGuard.hasAcknowledgedTerms]: Boolean(
getLocalStorage<boolean>({ getLocalStorage<boolean>({
@@ -59,12 +72,17 @@ const initialState: AccountState = {
[OnboardingGuard.hasPreviousTransactions]: undefined, [OnboardingGuard.hasPreviousTransactions]: undefined,
}, },
onboardingState: OnboardingState.Disconnected, onboardingState: OnboardingState.Disconnected,
subaccount: undefined,
// UI
clearedOrderIds: undefined,
hasUnseenFillUpdates: false,
hasUnseenOrderUpdates: false,
latestOrder: undefined,
uncommittedOrderClientIds: [], uncommittedOrderClientIds: [],
wallet: undefined, historicalPnlPeriod: undefined,
walletType: getLocalStorage<WalletType>({
key: LocalStorageKey.OnboardingSelectedWalletType, // Restriction
}), restriction: undefined,
}; };
export const accountSlice = createSlice({ export const accountSlice = createSlice({
@@ -124,6 +142,9 @@ export const accountSlice = createSlice({
state.historicalPnl = action.payload; state.historicalPnl = action.payload;
} }
}, },
setRestrictionType: (state, action: PayloadAction<Nullable<UsageRestriction>>) => {
state.restriction = action.payload;
},
setSubaccount: (state, action: PayloadAction<Nullable<Subaccount>>) => { setSubaccount: (state, action: PayloadAction<Nullable<Subaccount>>) => {
const existingOrderIds = state.subaccount?.orders const existingOrderIds = state.subaccount?.orders
? state.subaccount.orders.toArray().map((order) => order.id) ? state.subaccount.orders.toArray().map((order) => order.id)
@@ -178,6 +199,7 @@ export const {
setOnboardingGuard, setOnboardingGuard,
setOnboardingState, setOnboardingState,
setHistoricalPnl, setHistoricalPnl,
setRestrictionType,
setSubaccount, setSubaccount,
setWallet, setWallet,
viewedFills, viewedFills,
+10
View File
@@ -342,3 +342,13 @@ export const getBalances = (state: RootState) => state.account?.balances;
* @returns user wallet staking balances * @returns user wallet staking balances
* */ * */
export const getStakingBalances = (state: RootState) => state.account?.stakingBalances; 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 -1
View File
@@ -2,10 +2,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AbacusApiState } from '@/constants/abacus'; import { AbacusApiState } from '@/constants/abacus';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, type DydxNetwork } from '@/constants/networks'; import { DEFAULT_APP_ENVIRONMENT, type DydxNetwork } from '@/constants/networks';
import { getLocalStorage } from '@/lib/localStorage'; import { getLocalStorage } from '@/lib/localStorage';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export interface AppState { export interface AppState {
apiState: AbacusApiState | undefined; apiState: AbacusApiState | undefined;
@@ -19,6 +19,7 @@ const initialState: AppState = {
selectedNetwork: getLocalStorage({ selectedNetwork: getLocalStorage({
key: LocalStorageKey.SelectedNetwork, key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT, defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: validateAgainstAvailableEnvironments,
}), }),
}; };
+22 -25
View File
@@ -23,39 +23,36 @@ export const dialogsSlice = createSlice({
name: 'Dialogs', name: 'Dialogs',
initialState, initialState,
reducers: { reducers: {
addDialogToQueue: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => { openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
const dialogQueue = state.dialogQueue; if (state.activeDialog) {
dialogQueue.push(action.payload); state.dialogQueue.push(action.payload);
} else {
return { state.activeDialog = action.payload;
...state, }
dialogQueue, },
}; 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: ( openDialogInTradeBox: (
state: DialogsState, state: DialogsState,
action: PayloadAction<DialogInfo<TradeBoxDialogTypes>> action: PayloadAction<DialogInfo<TradeBoxDialogTypes>>
) => ({ ) => {
...state, state.activeTradeBoxDialog = action.payload;
activeTradeBoxDialog: action.payload, },
}),
}, },
}); });
export const { export const {
addDialogToQueue, forceOpenDialog,
closeDialog, closeDialog,
closeDialogInTradeBox, closeDialogInTradeBox,
openDialog, openDialog,
-42
View File
@@ -1,42 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
export interface NavigationState {
tradeLocation: {
hash: string;
key: string;
pathname: string;
search: string;
state: any;
};
}
export const CLEAR_TRADE_LOCATION = {
hash: '',
key: '',
pathname: '',
search: '',
state: null,
};
const initialState: NavigationState = {
tradeLocation: {
hash: '',
key: '',
pathname: '',
search: '',
state: null,
},
};
export const navigationSlice = createSlice({
name: 'Navigation',
initialState,
reducers: {
setTradeLocation: (state, { payload }) => ({
...state,
tradeLocation: payload,
}),
},
});
export const { setTradeLocation } = navigationSlice.actions;
-33
View File
@@ -1,33 +0,0 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { LocalStorageKey } from '@/constants/localStorage';
import { closeDialogInTradeBox } from '@/state/dialogs';
import { setTradeLocation } from '@/state/navigation';
import abacusStateManager from '@/lib/abacus';
import { setLocalStorage } from '@/lib/localStorage';
import { getMarketIdFromLocation } from '@/lib/tradeData';
export default (store: any) => (next: any) => async (action: PayloadAction<any>) => {
next(action);
const { type, payload } = action;
switch (type) {
case setTradeLocation.type: {
const marketId = getMarketIdFromLocation(payload);
if (marketId) {
abacusStateManager.setMarket(marketId);
store.dispatch(closeDialogInTradeBox());
setLocalStorage({ key: LocalStorageKey.LastViewedMarket, value: marketId });
}
break;
}
default: {
break;
}
}
};
+5 -1
View File
@@ -25,7 +25,11 @@ import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput'; import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
// import { TooltipContent } from '@/components/visx/TooltipContent'; // 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 abacusStateManager from '@/lib/abacus';
import { formatRelativeTime } from '@/lib/dateTime'; import { formatRelativeTime } from '@/lib/dateTime';
+2 -2
View File
@@ -27,9 +27,9 @@ export const DisconnectDialog = ({ setIsOpen }: ElementProps) => {
}; };
return ( return (
<Dialog isOpen setIsOpen={setIsOpen} title="Disconnect"> <Dialog isOpen setIsOpen={setIsOpen} title={stringGetter({ key: STRING_KEYS.DISCONNECT })}>
<Styled.Content> <Styled.Content>
<p>Are you sure you want to disconnect your account?</p> <p>{stringGetter({ key: STRING_KEYS.DISCONNECT_CONFIRMATION })}</p>
<Styled.ButtonRow> <Styled.ButtonRow>
<Close asChild> <Close asChild>
<Button action={ButtonAction.Destroy} onClick={disconnect}> <Button action={ButtonAction.Destroy} onClick={disconnect}>
+22 -2
View File
@@ -5,6 +5,9 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { AbacusApiStatus } from '@/constants/abacus'; import { AbacusApiStatus } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { UNICODE } from '@/constants/unicode';
import { useApiState, useStringGetter } from '@/hooks'; import { useApiState, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -29,8 +32,16 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const activeDialog = useSelector(getActiveDialog, shallowEqual); const activeDialog = useSelector(getActiveDialog, shallowEqual);
const showOfflineMessage =
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-3';
useEffect(() => { useEffect(() => {
if (activeDialog?.type === DialogTypes.ExchangeOffline && status === AbacusApiStatus.NORMAL) { if (
activeDialog?.type === DialogTypes.ExchangeOffline &&
status === AbacusApiStatus.NORMAL &&
!showOfflineMessage
) {
dispatch(closeDialog()); dispatch(closeDialog());
} }
}, [status, selectedNetwork]); }, [status, selectedNetwork]);
@@ -43,7 +54,16 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
title={stringGetter({ key: STRING_KEYS.UNAVAILABLE })} title={stringGetter({ key: STRING_KEYS.UNAVAILABLE })}
> >
<Styled.Content> <Styled.Content>
<p>{statusErrorMessage}</p> {showOfflineMessage ? (
<>
<p>Testnet is currently offline. Please check back!</p>
<Styled.Link href="https://v4-teacher.vercel.app/testnets/schedule">
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} {UNICODE.ARROW_RIGHT}
</Styled.Link>
</>
) : (
<p>{statusErrorMessage}</p>
)}
{import.meta.env.MODE !== 'production' && <NetworkSelectMenu />} {import.meta.env.MODE !== 'production' && <NetworkSelectMenu />}
</Styled.Content> </Styled.Content>
</Dialog> </Dialog>
@@ -19,7 +19,7 @@ import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins'; 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 = () => { export const ChooseWallet = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
@@ -34,7 +34,14 @@ export const ChooseWallet = () => {
<Styled.AlertMessage type={AlertType.Error}> <Styled.AlertMessage type={AlertType.Error}>
{ {
<h4> <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> </h4>
} }
{selectedWalletError} {selectedWalletError}
+42
View File
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RateLimitDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_MESSAGE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
+42
View File
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RestrictedGeoDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RestrictedWalletDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.WALLET_RESTRICTED_ERROR_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
@@ -10,9 +10,16 @@ import { TransferInputField, TransferInputTokenResource, TransferType } from '@/
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons'; import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; 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 { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -39,10 +46,11 @@ import { MustBigNumber } from '@/lib/numbers';
import { TokenSelectMenu } from './TokenSelectMenu'; import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt'; import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
import { join } from 'path';
export const WithdrawForm = () => { export const WithdrawForm = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { sendSquidWithdraw } = useSubaccount(); const { sendSquidWithdraw } = useSubaccount();
@@ -71,9 +79,11 @@ export const WithdrawForm = () => {
const { addTransferNotification } = useLocalNotifications(); const { addTransferNotification } = useLocalNotifications();
// Async Data // Async Data
const debouncedAmountBN = MustBigNumber(debouncedAmount); const debouncedAmountBN = useMemo(() => MustBigNumber(debouncedAmount), [debouncedAmount]);
const withdrawAmountBN = MustBigNumber(withdrawAmount); const freeCollateralBN = useMemo(
const freeCollateralBN = MustBigNumber(freeCollateral?.current); () => MustBigNumber(freeCollateral?.current),
[freeCollateral?.current]
);
useEffect(() => { useEffect(() => {
abacusStateManager.setTransferValue({ abacusStateManager.setTransferValue({
@@ -105,50 +115,85 @@ export const WithdrawForm = () => {
value: debouncedAmount, value: debouncedAmount,
field: TransferInputField.usdcSize, field: TransferInputField.usdcSize,
}); });
setError(null); setError(undefined);
} }
} catch (error) { } catch (error) {
setError(error); setError(error.message);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
setTransferValue(); setTransferValue();
}, [debouncedAmountBN.toNumber()]); }, [debouncedAmountBN]);
const { screenAddresses } = useDydxClient();
const { dydxAddress } = useAccounts();
const onSubmit = useCallback( const onSubmit = useCallback(
async (e: FormEvent) => { async (e: FormEvent) => {
try { try {
e.preventDefault(); e.preventDefault();
if (!requestPayload?.data || !debouncedAmountBN.toNumber()) { if (!requestPayload?.data || !debouncedAmountBN.toNumber() || !toAddress || !dydxAddress) {
throw new Error('Invalid request payload'); throw new Error('Invalid request payload');
} }
setIsLoading(true); setIsLoading(true);
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload?.data); setError(undefined);
if (txHash?.hash) { const screenResults = await screenAddresses({
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`; addresses: [toAddress, dydxAddress],
});
addTransferNotification({ if (screenResults?.[dydxAddress]) {
txHash: hash, setError(
fromChainId: TESTNET_CHAIN_ID, stringGetter({
toChainId: chainIdStr || undefined, key: STRING_KEYS.WALLET_RESTRICTED_WITHDRAWAL_TRANSFER_ORIGINATION_ERROR_MESSAGE,
toAmount: debouncedAmountBN.toNumber(), })
triggeredAt: Date.now(), );
}); } else if (screenResults?.[toAddress]) {
abacusStateManager.clearTransferInputValues(); setError(
setWithdrawAmount(''); 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) { } 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 { } finally {
setIsLoading(false); setIsLoading(false);
} }
}, },
[requestPayload, debouncedAmountBN, chainIdStr] [requestPayload, debouncedAmountBN, chainIdStr, toAddress, screenAddresses, stringGetter]
); );
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => { const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@@ -179,7 +224,7 @@ export const WithdrawForm = () => {
const onClickMax = useCallback(() => { const onClickMax = useCallback(() => {
setWithdrawAmount(freeCollateralBN.toString()); setWithdrawAmount(freeCollateralBN.toString());
}, [freeCollateral?.current, setWithdrawAmount]); }, [freeCollateralBN, setWithdrawAmount]);
const onSelectChain = useCallback((chain: string) => { const onSelectChain = useCallback((chain: string) => {
if (chain) { if (chain) {
@@ -215,7 +260,7 @@ export const WithdrawForm = () => {
value={freeCollateral?.current} value={freeCollateral?.current}
newValue={freeCollateral?.postOrder} newValue={freeCollateral?.postOrder}
sign={NumberSign.Negative} sign={NumberSign.Negative}
hasInvalidNewValue={withdrawAmountBN.minus(freeCollateralBN).isNegative()} hasInvalidNewValue={MustBigNumber(withdrawAmount).minus(freeCollateralBN).isNegative()}
withDiff={ withDiff={
Boolean(withdrawAmount) && !debouncedAmountBN.isNaN() && !debouncedAmountBN.isZero() Boolean(withdrawAmount) && !debouncedAmountBN.isNaN() && !debouncedAmountBN.isZero()
} }
@@ -224,18 +269,23 @@ export const WithdrawForm = () => {
}, },
]; ];
const { sanctionedAddresses } = useRestrictions();
const errorMessage = useMemo(() => { const errorMessage = useMemo(() => {
if (error) { if (error) {
return error?.message return stringGetter({
? stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE, params: { ERROR_MESSAGE: error },
params: { ERROR_MESSAGE: error.message }, });
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
} }
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS }); 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 (debouncedAmountBN) {
if (!chainIdStr) { if (!chainIdStr) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN }); return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN });
@@ -249,7 +299,15 @@ export const WithdrawForm = () => {
} }
return undefined; return undefined;
}, [error, freeCollateralBN, chainIdStr, debouncedAmountBN, toToken]); }, [
error,
freeCollateralBN,
chainIdStr,
debouncedAmountBN,
toToken,
toAddress,
sanctionedAddresses,
]);
const isDisabled = const isDisabled =
!!errorMessage || !!errorMessage ||
@@ -257,7 +315,8 @@ export const WithdrawForm = () => {
!chainIdStr || !chainIdStr ||
!toAddress || !toAddress ||
debouncedAmountBN.isNaN() || debouncedAmountBN.isNaN() ||
debouncedAmountBN.isZero(); debouncedAmountBN.isZero() ||
isLoading;
return ( return (
<Styled.Form onSubmit={onSubmit}> <Styled.Form onSubmit={onSubmit}>
+68 -34
View File
@@ -3,8 +3,6 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type NumberFormatValues } from 'react-number-format'; import { type NumberFormatValues } from 'react-number-format';
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from 'react-redux';
import type { SyntheticInputEvent } from 'react-number-format/types/types'; 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 { validation } from '@dydxprotocol/v4-client-js';
import { TransferInputField, TransferType } from '@/constants/abacus'; import { TransferInputField, TransferType } from '@/constants/abacus';
@@ -12,7 +10,7 @@ import { AlertType } from '@/constants/alerts';
import { ButtonShape, ButtonSize } from '@/constants/buttons'; import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers'; import { NumberSign } from '@/constants/numbers';
import { import {
DYDX_CHAIN_ASSET_COIN_DENOM, DYDX_CHAIN_ASSET_COIN_DENOM,
DYDX_CHAIN_ASSET_TAGS, DYDX_CHAIN_ASSET_TAGS,
@@ -22,6 +20,8 @@ import {
import { import {
useAccountBalance, useAccountBalance,
useAccounts, useAccounts,
useDydxClient,
useRestrictions,
useSelectedNetwork, useSelectedNetwork,
useStringGetter, useStringGetter,
useSubaccount, useSubaccount,
@@ -77,7 +77,7 @@ export const TransferForm = ({
const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset); const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset);
// Form states // Form states
const [error, setError] = useState<Error | undefined>(); const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance; const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
@@ -96,16 +96,6 @@ export const TransferForm = ({
const balanceBN = MustBigNumber(balance); const balanceBN = MustBigNumber(balance);
const newBalanceBN = MustBigNumber(newBalance); 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(() => { useEffect(() => {
abacusStateManager.setTransferValue({ abacusStateManager.setTransferValue({
value: TransferType.transferOut.rawValue, value: TransferType.transferOut.rawValue,
@@ -125,6 +115,21 @@ export const TransferForm = ({
}); });
}, [asset]); }, [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 () => { const onTransfer = async () => {
if (!isAmountValid || !isAddressValid || !fee) return; if (!isAmountValid || !isAddressValid || !fee) return;
setIsLoading(true); setIsLoading(true);
@@ -136,20 +141,51 @@ export const TransferForm = ({
asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN
).toNumber(); ).toNumber();
const txResponse = await transfer( const screenResults = await screenAddresses({
amountToTransfer, addresses: [recipientAddress!, dydxAddress!],
recipientAddress as string, });
DYDX_CHAIN_ASSET_COIN_DENOM[asset]
);
if (txResponse?.code === 0) { if (screenResults?.[dydxAddress!]) {
console.log('TransferForm > txReceipt > ', txResponse?.hash); setError(
onDone?.(); 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 { } else {
throw new Error(txResponse?.rawLog ?? 'Transaction did not commit.'); const txResponse = await transfer(
amountToTransfer,
recipientAddress as string,
DYDX_CHAIN_ASSET_COIN_DENOM[asset]
);
if (txResponse?.code === 0) {
console.log('TransferForm > txReceipt > ', txResponse?.hash);
onDone?.();
} else {
throw new Error(txResponse?.rawLog ?? 'Transaction did not commit.');
}
} }
} catch (error) { } 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); log('TransferForm/onTransfer', error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -231,13 +267,6 @@ export const TransferForm = ({
}, },
]; ];
const addressValidationErrorMessage = stringGetter({
key:
dydxAddress === recipientAddress
? STRING_KEYS.TRANSFER_TO_YOURSELF
: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
});
const renderFormInputButton = ({ const renderFormInputButton = ({
label, label,
isInputEmpty, isInputEmpty,
@@ -302,7 +331,12 @@ export const TransferForm = ({
{recipientAddress && !isAddressValid && ( {recipientAddress && !isAddressValid && (
<Styled.AddressValidationAlertMessage type={AlertType.Error}> <Styled.AddressValidationAlertMessage type={AlertType.Error}>
{addressValidationErrorMessage} {stringGetter({
key:
dydxAddress === recipientAddress
? STRING_KEYS.TRANSFER_TO_YOURSELF
: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
})}
</Styled.AddressValidationAlertMessage> </Styled.AddressValidationAlertMessage>
)} )}
@@ -342,13 +376,13 @@ export const TransferForm = ({
</AlertMessage> </AlertMessage>
)} )}
{error && <AlertMessage type={AlertType.Error}>{error.message}</AlertMessage>} {error && <AlertMessage type={AlertType.Error}>{error}</AlertMessage>}
<Styled.Footer> <Styled.Footer>
<TransferButtonAndReceipt <TransferButtonAndReceipt
selectedAsset={asset} selectedAsset={asset}
fees={fee || undefined} fees={fee || undefined}
isDisabled={!isAmountValid || !isAddressValid || !fee} isDisabled={!isAmountValid || !isAddressValid || !fee || isLoading}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)} isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
/> />
</Styled.Footer> </Styled.Footer>