Compare commits

...
Author SHA1 Message Date
Bill He 238b5456b7 remove arb 2024-02-15 15:44:29 -08:00
Bill He 74961b8da5 Enable CCTP onboarding for arbitrum, avalanche, optimism 2024-02-15 13:55:44 -08:00
Bill He 92481e29fc Enable CCTP for arbitrum avalanche optimism 2024-02-15 13:44:10 -08:00
aleka c92f218493 bump abacus + use new env config format (#298)
* bump abacus + use new env config format

* use new config path

* bump abacus to 1.4.5

* util function for getting mintscan link
2024-02-15 13:38:05 -05:00
moo-onthelawn b09fe9ebb1 fix bug (#304) 2024-02-14 15:19:37 -08:00
Bill 1c28505ec0 Set minimum for CCTP withdraws (#306) 2024-02-14 14:59:04 -08:00
moo-onthelawn 5531e21c76 change text color (#302) 2024-02-14 14:29:38 -05:00
John Huang 4ebeb21841 MOB-231 Refactor env.json to env2.json (#303)
* MOB-231 Refactor env.json to env2.json

* Move to v1/env.json

* dydx-testnet-4
2024-02-14 13:37:51 -05:00
moo-onthelawn b638ec1ad6 CT-463 Add Position (entry) line to TradingView (#292)
* wip

* fix deleted line issue

* compress tgz changes

* fix dependency hook

* simplify

* typo

* add trailing percent

* fix bug

* fix order

* merge order types

* wip

* rename variable

* review comments

* remove file

* rebase on new changes

* remove log

* clean up chart line type

* clean up typings a bit more

* fix var rename

* review comments

* clean up logic

* typing
2024-02-14 09:41:04 -05:00
Jared Vu f7a1cfc67e Updated Keplr language (#300) 2024-02-13 17:07:22 -08:00
aleka d66a776ad2 show trading rewards in testnet (#299) 2024-02-13 16:02:10 -05:00
moo-onthelawn e3e00f4f1e CT-145 Add warning icon + tooltip to trade order button CTA (#297)
* update

* rename to config
2024-02-13 16:00:46 -05:00
aleka 9ea578b8e4 avoid logging expected switch chain error (#291)
* avoid logging expected switch chain error

* skip logging more expected errors

* update switch chain error requirements check
2024-02-13 13:51:47 -05:00
moo-onthelawn f47fda45bf CT-428 Update browser tab title to be mid market price (#294)
* calculation

* make new selectors
2024-02-13 13:49:24 -05:00
48 changed files with 1427 additions and 456 deletions
+2 -2
View File
@@ -40,9 +40,9 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.4.2",
"@dydxprotocol/v4-abacus": "^1.4.5",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.28",
"@dydxprotocol/v4-localization": "^1.1.30",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
+12 -12
View File
@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
follow-redirects: 1.15.3
@@ -30,14 +26,14 @@ dependencies:
specifier: ^0.32.1
version: 0.32.2
'@dydxprotocol/v4-abacus':
specifier: ^1.4.2
version: 1.4.2
specifier: ^1.4.5
version: 1.4.5
'@dydxprotocol/v4-client-js':
specifier: ^1.0.20
version: 1.0.20
'@dydxprotocol/v4-localization':
specifier: ^1.1.28
version: 1.1.28
specifier: ^1.1.30
version: 1.1.30
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -1290,8 +1286,8 @@ packages:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
dev: false
/@dydxprotocol/v4-abacus@1.4.2:
resolution: {integrity: sha512-+hugk0RulMwMthR2xCMYXohcC3sEYqVW/lmiq0RUuHZ9yrjmgy48xl0aZUmXGUYXyoiHXPS4AULhRKHQ4OOLwg==}
/@dydxprotocol/v4-abacus@1.4.5:
resolution: {integrity: sha512-LhJmpIaUkHCsSiHx+jdk+59euvGp2E+gGFelpfmOYpH1+enZgEXDQvWsgHSFEQxYP3mRiGG4uo+ZYNGdvffg7g==}
dev: false
/@dydxprotocol/v4-client-js@1.0.20:
@@ -1323,8 +1319,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.1.28:
resolution: {integrity: sha512-R8hGDbJvZb9HsI0jwYIYDS3aNM3T0aLlXOnogihGrRRibZs874XJy5fCGBW2aRXAyDu56nbjkyuOAQCkF3KP1Q==}
/@dydxprotocol/v4-localization@1.1.30:
resolution: {integrity: sha512-TZfWWRSOxcjLHs972wlJVVHkE7+DVqAUnGZSs24HYHsPtUkPhZiNXMOA2Vk9YddQxumhM79xIRH0cmJSe5DDUg==}
dev: false
/@dydxprotocol/v4-proto@4.0.0-dev.0:
@@ -16053,3 +16049,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+21 -11
View File
@@ -1,12 +1,22 @@
[
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
}
]
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
},
{
"chainId": "43114",
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"name": "Avalanche"
},
{
"chainId": "10",
"tokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"name": "optimism"
}
]
+1 -1
View File
@@ -1329,4 +1329,4 @@
}
}
}
}
}
+638
View File
@@ -0,0 +1,638 @@
{
"apps": {
"ios": {
"scheme": "dydx-t-v4"
}
},
"tokens": {
"dydxprotocol-testnet": {
"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"
}
},
"dydx-testnet-4": {
"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"
}
},
"[mainnet chain id]": {
"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"
}
}
},
"links": {
"dydxprotocol-testnet": {
"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}",
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"dydx-testnet-4": {
"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://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"[mainnet chain id]": {
"tos": "[HTTP link to TOS]",
"privacy": "[HTTP link to Privacy Policy]",
"statusPage": "[HTTP link to status page]",
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
"mintscanBase": "[HTTP link to TOS mintscan base url]",
"feedback": "[HTTP link to feedback form, can be null]",
"blogs": "[HTTP link to blogs, can be null]",
"foundation": "[HTTP link to foundation, can be null]",
"reduceOnlyLearnMore": "[HTTP link to reduce-only learn more, can be null]",
"documentation": "[HTTP link to documentation, can be null]",
"community": "[HTTP link to community, can be null]",
"help": "[HTTP link to help page, can be null]",
"governanceLearnMore": "[HTTP link to governance learn more, can be null]",
"newMarketProposalLearnMore": "[HTTP link to new market proposal learn more, can be null]",
"stakingLearnMore": "[HTTP link to staking learn more, can be null]",
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
}
},
"wallets": {
"dydxprotocol-testnet": {
"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"
},
"dydx-testnet-4": {
"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"
},
"[mainnet chain id]": {
"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"
}
},
"governance": {
"dydxprotocol-testnet": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"dydx-testnet-4": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"[mainnet chain id]": {
"newMarketProposal": {
"initialDepositAmount": 0,
"delayBlocks": 0,
"newMarketsMethodology": "[URL to spreadsheet or document that explains methodology]"
}
}
},
"deployments": {
"MAINNET": {
"environments": [
"dydxprotocol-mainnet"
],
"default": "dydxprotocol-mainnet"
},
"TESTFLIGHT": {
"environments": [
"dydxprotocol-mainnet",
"dydxprotocol-testnet"
],
"default": "dydxprotocol-mainnet"
},
"TESTNET": {
"environments": [
"dydxprotocol-testnet"
],
"default": "dydxprotocol-testnet"
},
"DEV": {
"environments": [
"dydxprotocol-dev",
"dydxprotocol-dev-2",
"dydxprotocol-dev-4",
"dydxprotocol-dev-5",
"dydxprotocol-staging",
"dydxprotocol-staging-west",
"dydxprotocol-testnet",
"dydxprotocol-testnet-dydx",
"dydxprotocol-testnet-nodefleet",
"dydxprotocol-testnet-kingnodes",
"dydxprotocol-testnet-liquify",
"dydxprotocol-testnet-polkachu",
"dydxprotocol-testnet-bware"
],
"default": "dydxprotocol-testnet"
}
},
"environments": {
"dydxprotocol-dev": {
"name": "v4 Dev",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4dev.dydx.exchange",
"socket": "wss://indexer.v4dev.dydx.exchange"
}
],
"validators": [
"https://validator.v4dev.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4dev.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-2": {
"name": "v4 Dev 2",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "http://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com",
"socket": "ws://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com"
}
],
"validators": [
"http://54.92.118.111"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-4": {
"name": "v4 Dev 4",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4dev4.dydx.exchange",
"socket": "wss://indexer.v4dev4.dydx.exchange"
}
],
"validators": [
"https://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4dev4.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-5": {
"name": "v4 Dev 5",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "http://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com",
"socket": "ws://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com"
}
],
"validators": [
"http://18.223.78.50"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging": {
"name": "v4 Staging",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"apps": {
"ios": {
"minimalVersion": "1.0",
"build": 40000,
"url": "https://apps.apple.com/app/dydx/id1564787350"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator-uswest1.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet": {
"name": "v4 Public Testnet",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-testnet-full-rpc.public.blastapi.io/",
"https://dydx-testnet-rpc.polkachu.com/",
"https://dydx-testnet.nodefleet.org",
"https://test-dydx.kingnodes.com",
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"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",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-testnet.nodefleet.org"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://test-dydx.kingnodes.com"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-testnet-rpc.polkachu.com/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/BWare",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-testnet-full-rpc.public.blastapi.io/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-mainnet": {
"name": "v4",
"ethereumChainId": "1",
"dydxChainId": "[mainnet chain id]",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "[mainnet squid integrator id]",
"isMainNet": true,
"endpoints": {
"indexers": [
{
"api": "[REST endpoint]",
"socket": "[Websocket endpoint]"
}
],
"validators": [
"[Validator endpoint 1",
"[Validator endpoint n]"
],
"0xsquid": "[0xSquid endpoint for mainnet]",
"nobleValidator": "[noble validator endpoint for mainnet]"
},
"featureFlags": {
"reduceOnlySupported": false
}
}
}
}
+5
View File
@@ -33,4 +33,9 @@ TagStory.argTypes = {
control: { type: 'select' },
defaultValue: undefined,
},
isHighlighted: {
options: [true, false],
control: { type: 'select' },
defaultValue: false,
},
};
+1
View File
@@ -76,5 +76,6 @@ export const Tag = styled.span<StyleProps>`
isHighlighted &&
css`
background-color: var(--color-accent);
color: var(--color-text-button);
`}
`;
+6 -1
View File
@@ -1,4 +1,4 @@
import environments from '../../public/configs/env.json';
import environments from '../../public/configs/v1/env.json';
const CURRENT_MODE = ({
production: 'MAINNET',
@@ -14,5 +14,10 @@ export const isDev = CURRENT_MODE === 'DEV';
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
export const TOKEN_CONFIG_MAP = environments.tokens;
export const LINKS_CONFIG_MAP = environments.links;
export const WALLETS_CONFIG_MAP = environments.wallets;
export const GOVERNANCE_CONFIG_MAP = environments.governance;
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
export type DydxChainId = keyof typeof TOKEN_CONFIG_MAP;
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
+1
View File
@@ -22,4 +22,5 @@ export enum NumberSign {
// Deposit/Withdraw
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
export const MIN_CCTP_TRANSFER_AMOUNT = 10;
export const MAX_PRICE_IMPACT = 0.02; // 2%
+9 -1
View File
@@ -1,4 +1,12 @@
import { AppColorMode } from '@/state/configs';
import type { ThemeName } from 'public/tradingview/charting_library';
import { AppColorMode, AppTheme } from '@/state/configs';
export const THEME_NAMES: Record<AppTheme, ThemeName> = {
[AppTheme.Classic]: 'Classic',
[AppTheme.Dark]: 'Dark',
[AppTheme.Light]: 'Light',
};
export type Theme = {
[AppColorMode.GreenUp]: ThemeColorBase;
+16
View File
@@ -0,0 +1,16 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import type {
IChartingLibraryWidget,
IOrderLineAdapter,
IPositionLineAdapter,
} from 'public/tradingview/charting_library';
export type TvWidget = IChartingLibraryWidget & { _id?: string; _ready?: boolean };
export type ChartLineType = OrderSide | 'position';
export type ChartLine = {
line: IOrderLineAdapter | IPositionLineAdapter;
chartLineType: ChartLineType;
};
+5 -4
View File
@@ -24,7 +24,7 @@ import {
import { isMetaMask } from '@/lib/wallet/providers';
import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
import { DydxChainId, WALLETS_CONFIG_MAP } from './networks';
// Wallet connection types
@@ -40,6 +40,7 @@ export enum WalletErrorType {
// General
ChainMismatch,
UserCanceled,
SwitchChainMethodMissing,
// Non-Deterministic
NonDeterministicWallet,
@@ -290,17 +291,17 @@ export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
/**
* @description typed data to sign for dYdX Chain onboarding
*/
export const getSignTypedData = (selectedNetwork: DydxNetwork) =>
export const getSignTypedData = (selectedDydxChainId: DydxChainId) =>
({
primaryType: 'dYdX',
domain: {
name: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataDomainName,
name: WALLETS_CONFIG_MAP[selectedDydxChainId].signTypedDataDomainName,
},
types: {
dYdX: [{ name: 'action', type: 'string' }],
},
message: {
action: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataAction,
action: WALLETS_CONFIG_MAP[selectedDydxChainId].signTypedDataAction,
},
} as const);
+2
View File
@@ -1,2 +1,4 @@
export { useChartLines } from './useChartLines';
export { useChartMarketAndResolution } from './useChartMarketAndResolution';
export { useTradingView } from './useTradingView';
export { useTradingViewTheme } from './useTradingViewTheme';
+209
View File
@@ -0,0 +1,209 @@
import { useEffect, useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { AbacusOrderStatus, ORDER_SIDES, SubaccountOrder } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { type OrderType, ORDER_TYPE_STRINGS } from '@/constants/trade';
import type { ChartLine, TvWidget } from '@/constants/tvchart';
import { useStringGetter } from '@/hooks';
import { getCurrentMarketOrders, getCurrentMarketPositionData } from '@/state/accountSelectors';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import { getChartLineColors } from '@/lib/tradingView/utils';
let chartLines: Record<string, ChartLine> = {};
/**
* @description Hook to handle drawing chart lines
*/
export const useChartLines = ({
tvWidget,
displayButton,
isChartReady,
}: {
tvWidget: TvWidget | null;
displayButton: HTMLElement | null;
isChartReady?: boolean;
}) => {
const [showOrderLines, setShowOrderLines] = useState(false);
const stringGetter = useStringGetter();
const appTheme = useSelector(getAppTheme);
const appColorMode = useSelector(getAppColorMode);
const currentMarketPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
const currentMarketOrders: SubaccountOrder[] = useSelector(getCurrentMarketOrders, shallowEqual);
useEffect(() => {
if (isChartReady && displayButton) {
displayButton.onclick = () => {
const newShowOrderLinesState = !showOrderLines;
if (newShowOrderLinesState) {
displayButton?.classList?.add('order-lines-active');
} else {
displayButton?.classList?.remove('order-lines-active');
}
setShowOrderLines(newShowOrderLinesState);
};
}
}, [isChartReady, showOrderLines]);
useEffect(() => {
if (tvWidget && isChartReady) {
tvWidget.onChartReady(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
drawPositionLine();
} else {
deleteChartLines();
}
});
});
}
}, [isChartReady, showOrderLines, currentMarketPositionData, currentMarketOrders]);
const drawPositionLine = () => {
if (!currentMarketPositionData) return;
const entryPrice = currentMarketPositionData.entryPrice?.current;
const size = currentMarketPositionData.size?.current;
const key = currentMarketPositionData.id;
const price = MustBigNumber(entryPrice).toNumber();
const maybePositionLine = chartLines[key]?.line;
const shouldShow = size && size !== 0;
if (!shouldShow) {
if (maybePositionLine) {
maybePositionLine.remove();
delete chartLines[key];
return;
}
} else {
const quantity = size.toString();
if (maybePositionLine) {
if (maybePositionLine.getQuantity() !== quantity) {
maybePositionLine.setQuantity(quantity);
}
if (maybePositionLine.getPrice() !== price) {
maybePositionLine.setPrice(price);
}
} else {
const positionLine = tvWidget
?.chart()
.createPositionLine({ disableUndo: false })
.setText(stringGetter({ key: STRING_KEYS.ENTRY_PRICE_SHORT }))
.setPrice(price)
.setQuantity(quantity);
if (positionLine) {
const chartLine = { line: positionLine, chartLineType: 'position' };
setLineColors({ chartLine: chartLine });
chartLines[key] = chartLine;
}
}
}
};
const drawOrderLines = () => {
if (!currentMarketOrders) return;
currentMarketOrders.forEach(
({
id,
type,
status,
side,
cancelReason,
remainingSize,
size,
triggerPrice,
price,
trailingPercent,
}) => {
const key = id;
const quantity = (remainingSize ?? size).toString();
const orderType = type.rawValue as OrderType;
const orderLabel = stringGetter({
key: ORDER_TYPE_STRINGS[orderType].orderTypeKey,
});
const orderString = trailingPercent ? `${orderLabel} ${trailingPercent}%` : orderLabel;
const shouldShow =
!cancelReason &&
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
const maybeOrderLine = chartLines[key]?.line;
if (!shouldShow) {
if (maybeOrderLine) {
maybeOrderLine.remove();
delete chartLines[key];
return;
}
} else {
if (maybeOrderLine) {
if (maybeOrderLine.getQuantity() !== quantity) {
maybeOrderLine.setQuantity(quantity);
}
} else {
const orderLine = tvWidget
?.chart()
.createOrderLine({ disableUndo: false })
.setPrice(MustBigNumber(triggerPrice ?? price).toNumber())
.setQuantity(quantity)
.setText(orderString);
if (orderLine) {
const chartLine: ChartLine = {
line: orderLine,
chartLineType: ORDER_SIDES[side.name],
};
setLineColors({ chartLine: chartLine });
chartLines[key] = chartLine;
}
}
}
}
);
};
const deleteChartLines = () => {
Object.values(chartLines).forEach(({ line }) => {
line.remove();
});
chartLines = {};
};
const setLineColors = ({ chartLine }: { chartLine: ChartLine }) => {
const { line, chartLineType } = chartLine;
const { maybeQuantityColor, borderColor, backgroundColor, textColor, textButtonColor } =
getChartLineColors({
appTheme,
appColorMode,
chartLineType,
});
line
.setQuantityBorderColor(borderColor)
.setBodyBackgroundColor(backgroundColor)
.setBodyBorderColor(borderColor)
.setBodyTextColor(textColor)
.setQuantityTextColor(textButtonColor);
maybeQuantityColor &&
line.setLineColor(maybeQuantityColor).setQuantityBackgroundColor(maybeQuantityColor);
};
return { chartLines };
};
@@ -0,0 +1,72 @@
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import type { ResolutionString } from 'public/tradingview/charting_library';
import { DEFAULT_RESOLUTION, RESOLUTION_CHART_CONFIGS } from '@/constants/candles';
import { DEFAULT_MARKETID } from '@/constants/markets';
import type { TvWidget } from '@/constants/tvchart';
import { setTvChartResolution } from '@/state/perpetuals';
import { getCurrentMarketId, getSelectedResolutionForMarket } from '@/state/perpetualsSelectors';
/**
* @description Hook to handle changing markets and setting chart resolution
*/
export const useChartMarketAndResolution = ({
tvWidget,
isWidgetReady,
savedResolution,
}: {
tvWidget: TvWidget | null;
isWidgetReady?: boolean;
savedResolution?: ResolutionString;
}) => {
const dispatch = useDispatch();
const currentMarketId: string = useSelector(getCurrentMarketId) || DEFAULT_MARKETID;
const selectedResolution: string =
useSelector(getSelectedResolutionForMarket(currentMarketId)) || DEFAULT_RESOLUTION;
const chart = isWidgetReady ? tvWidget?.chart() : undefined;
const chartResolution = chart?.resolution?.();
/**
* @description Hook to handle changing markets - intentionally should avoid triggering on change of resolutions.
*/
useEffect(() => {
if (currentMarketId && isWidgetReady) {
const resolution = savedResolution || selectedResolution;
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
}
}, [currentMarketId, isWidgetReady]);
/**
* @description Hook to handle changing chart resolution
*/
useEffect(() => {
if (chartResolution) {
if (chartResolution !== selectedResolution) {
dispatch(setTvChartResolution({ marketId: currentMarketId, resolution: chartResolution }));
}
setVisibleRangeForResolution({ resolution: chartResolution });
}
}, [currentMarketId, chartResolution, selectedResolution]);
const setVisibleRangeForResolution = ({ resolution }: { resolution: ResolutionString }) => {
// Different resolutions have different timeframes to display data efficiently.
const { defaultRange } = RESOLUTION_CHART_CONFIGS[resolution];
// from/to values converted to epoch seconds
const newRange = {
from: (Date.now() - defaultRange) / 1000,
to: Date.now() / 1000,
};
tvWidget?.activeChart().setVisibleRange(newRange, { percentRightMargin: 10 });
};
};
+18 -14
View File
@@ -1,17 +1,19 @@
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import isEmpty from 'lodash/isEmpty';
import { LanguageCode, ResolutionString, widget } from 'public/tradingview/charting_library';
import { DEFAULT_RESOLUTION } from '@/constants/candles';
import { SUPPORTED_LOCALE_BASE_TAGS, STRING_KEYS } from '@/constants/localization';
import { LocalStorageKey } from '@/constants/localStorage';
import type { TvWidget } from '@/constants/tvchart';
import { useDydxClient, useLocalStorage, useStringGetter } from '@/hooks';
import { store } from '@/state/_store';
import { store } from '@/state/_store';
import { getSelectedNetwork } from '@/state/appSelectors';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { getSelectedLocale } from '@/state/localizationSelectors';
@@ -28,8 +30,8 @@ export const useTradingView = ({
displayButtonRef,
setIsChartReady,
}: {
tvWidgetRef: React.MutableRefObject<any>;
displayButtonRef: React.MutableRefObject<any>;
tvWidgetRef: React.MutableRefObject<TvWidget | null>;
displayButtonRef: React.MutableRefObject<HTMLElement | null>;
setIsChartReady: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const stringGetter = useStringGetter();
@@ -69,15 +71,17 @@ export const useTradingView = ({
tvWidgetRef.current = tvChartWidget;
tvWidgetRef.current.onChartReady(() => {
tvWidgetRef?.current?.headerReady().then(() => {
displayButtonRef.current = tvWidgetRef?.current?.createButton();
displayButtonRef.current.innerHTML = `<span>${stringGetter({
key: STRING_KEYS.ORDER_LINES,
})}</span> <div class="displayOrdersButton-toggle"></div>`;
displayButtonRef.current.setAttribute(
'title',
stringGetter({ key: STRING_KEYS.ORDER_LINES_TOOLTIP })
);
tvWidgetRef.current?.headerReady().then(() => {
if (displayButtonRef && tvWidgetRef.current) {
displayButtonRef.current = tvWidgetRef.current.createButton();
displayButtonRef.current.innerHTML = `<span>${stringGetter({
key: STRING_KEYS.ORDER_LINES,
})}</span> <div class="displayOrdersButton-toggle"></div>`;
displayButtonRef.current.setAttribute(
'title',
stringGetter({ key: STRING_KEYS.ORDER_LINES_TOOLTIP })
);
}
});
tvWidgetRef?.current?.subscribe('onAutoSaveNeeded', () =>
+55 -63
View File
@@ -1,16 +1,14 @@
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import type {
IChartingLibraryWidget,
IOrderLineAdapter,
ThemeName,
} from 'public/tradingview/charting_library';
import { THEME_NAMES } from '@/constants/styles/colors';
import type { ChartLine, TvWidget } from '@/constants/tvchart';
import { AppColorMode, AppTheme } from '@/state/configs';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { getWidgetOverrides, getOrderLineColors } from '@/lib/tradingView/utils';
import { getWidgetOverrides, getChartLineColors } from '@/lib/tradingView/utils';
/**
* @description Method to define a type guard and check that an element is an IFRAME
@@ -26,12 +24,12 @@ const isIFrame = (element: HTMLElement | null): element is HTMLIFrameElement =>
* In order to support our Classic along with Dark/Light, we are directly accessing the <html> within the iFrame.
*/
export const useTradingViewTheme = ({
orderLines,
chartLines,
tvWidget,
isWidgetReady,
}: {
orderLines: Record<string, IOrderLineAdapter>;
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
chartLines: Record<string, ChartLine>;
tvWidget: TvWidget | null;
isWidgetReady?: boolean;
}) => {
const appTheme: AppTheme = useSelector(getAppTheme);
@@ -39,70 +37,64 @@ export const useTradingViewTheme = ({
useEffect(() => {
if (tvWidget && isWidgetReady) {
tvWidget
.changeTheme?.(
{
[AppTheme.Classic]: '',
[AppTheme.Dark]: 'dark',
[AppTheme.Light]: 'light',
}[appTheme] as ThemeName
)
.then(() => {
const tvChartId = tvWidget?._id;
tvWidget.changeTheme?.(THEME_NAMES[appTheme]).then(() => {
const tvChartId = tvWidget?._id;
if (tvChartId) {
const frame = document?.getElementById(tvChartId);
if (tvChartId) {
const frame = document?.getElementById(tvChartId);
if (isIFrame(frame) && frame.contentWindow) {
const innerHtml = frame.contentWindow.document.documentElement;
switch (appTheme) {
case AppTheme.Classic:
innerHtml?.classList.remove('theme-dark', 'theme-light');
break;
case AppTheme.Dark:
innerHtml?.classList.remove('theme-light');
innerHtml?.classList.add('theme-dark');
break;
case AppTheme.Light:
innerHtml?.classList.remove('theme-dark');
innerHtml?.classList.add('theme-light');
}
if (isIFrame(frame) && frame.contentWindow) {
const innerHtml = frame.contentWindow.document.documentElement;
switch (appTheme) {
case AppTheme.Classic:
innerHtml?.classList.remove('theme-dark', 'theme-light');
break;
case AppTheme.Dark:
innerHtml?.classList.remove('theme-light');
innerHtml?.classList.add('theme-dark');
break;
case AppTheme.Light:
innerHtml?.classList.remove('theme-dark');
innerHtml?.classList.add('theme-light');
}
}
}
const { overrides, studies_overrides } = getWidgetOverrides({ appTheme, appColorMode });
tvWidget?.applyOverrides(overrides);
tvWidget?.applyStudiesOverrides(studies_overrides);
const { overrides, studies_overrides } = getWidgetOverrides({ appTheme, appColorMode });
tvWidget?.applyOverrides(overrides);
tvWidget?.applyStudiesOverrides(studies_overrides);
// Necessary to update existing indicators
const volumeStudyId = tvWidget
?.activeChart()
?.getAllStudies()
?.find((x) => x.name === 'Volume')?.id;
// Necessary to update existing indicators
const volumeStudyId = tvWidget
?.activeChart()
?.getAllStudies()
?.find((x) => x.name === 'Volume')?.id;
if (volumeStudyId) {
const volume = tvWidget?.activeChart()?.getStudyById(volumeStudyId);
volume.applyOverrides({
'volume.color.0': studies_overrides['volume.volume.color.0'],
'volume.color.1': studies_overrides['volume.volume.color.1'],
});
if (volumeStudyId) {
const volume = tvWidget?.activeChart()?.getStudyById(volumeStudyId);
volume.applyOverrides({
'volume.color.0': studies_overrides['volume.volume.color.0'],
'volume.color.1': studies_overrides['volume.volume.color.1'],
});
}
// Necessary to update existing chart lines
Object.values(chartLines).forEach(({ chartLineType, line }) => {
const { maybeQuantityColor, borderColor, backgroundColor, textColor, textButtonColor } =
getChartLineColors({ chartLineType: chartLineType, appTheme, appColorMode });
if (maybeQuantityColor) {
line.setLineColor(maybeQuantityColor).setQuantityBackgroundColor(maybeQuantityColor);
}
// Necessary to update existing chart lines
Object.entries(orderLines).forEach(([key, line]) => {
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
getOrderLineColors({ side: key.split('-')[0], appTheme, appColorMode });
line
.setLineColor(orderColor)
.setQuantityBackgroundColor(orderColor)
.setQuantityBorderColor(borderColor)
.setBodyBackgroundColor(backgroundColor)
.setBodyBorderColor(borderColor)
.setBodyTextColor(textColor)
.setQuantityTextColor(textButtonColor);
});
line
.setQuantityBorderColor(borderColor)
.setBodyBackgroundColor(backgroundColor)
.setBodyBorderColor(borderColor)
.setBodyTextColor(textColor)
.setQuantityTextColor(textButtonColor);
});
});
}
}, [appTheme, appColorMode, isWidgetReady]);
};
+8 -8
View File
@@ -18,14 +18,13 @@ import type { ResolutionString } from 'public/tradingview/charting_library';
import type { ConnectNetworkEvent, NetworkConfig } from '@/constants/abacus';
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
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';
import { useTokenConfigs } from './useTokenConfigs';
type DydxContextType = ReturnType<typeof useDydxClientContext>;
const DydxContext = createContext<DydxContextType>({} as DydxContextType);
@@ -41,7 +40,8 @@ const useDydxClientContext = () => {
// ------ Network ------ //
const selectedNetwork = useSelector(getSelectedNetwork);
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
const { usdcDenom, usdcDecimals, usdcGasDenom, chainTokenDenom, chainTokenDecimals } =
useTokenConfigs();
const [networkConfig, setNetworkConfig] = useState<NetworkConfig>();
@@ -75,11 +75,11 @@ const useDydxClientContext = () => {
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,
USDC_DENOM: usdcDenom,
USDC_DECIMALS: usdcDecimals,
USDC_GAS_DENOM: usdcGasDenom,
CHAINTOKEN_DENOM: chainTokenDenom,
CHAINTOKEN_DECIMALS: chainTokenDecimals,
},
{
broadcastPollIntervalMs: 3_000,
+7 -4
View File
@@ -1,5 +1,8 @@
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useSelectedNetwork } from '@/hooks';
import { useSelector } from 'react-redux';
import { GOVERNANCE_CONFIG_MAP } from '@/constants/networks';
import { getSelectedDydxChainId } from '@/state/appSelectors';
export interface GovernanceVariables {
newMarketProposal: {
@@ -10,7 +13,7 @@ export interface GovernanceVariables {
}
export const useGovernanceVariables = (): GovernanceVariables => {
const { selectedNetwork } = useSelectedNetwork();
const governanceVars = ENVIRONMENT_CONFIG_MAP[selectedNetwork].governance as GovernanceVariables;
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const governanceVars = GOVERNANCE_CONFIG_MAP[selectedDydxChainId] as GovernanceVariables;
return governanceVars;
};
+4 -4
View File
@@ -5,7 +5,6 @@ import { isEqual, groupBy } from 'lodash';
import { useNavigate } from 'react-router-dom';
import { DialogTypes } from '@/constants/dialogs';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { AppRoute } from '@/constants/routes';
import { DydxChainAsset } from '@/constants/wallets';
@@ -23,7 +22,7 @@ import {
TransferNotificationTypes,
} from '@/constants/notifications';
import { useSelectedNetwork, useStringGetter } from '@/hooks';
import { useStringGetter } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
@@ -36,6 +35,7 @@ import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelector
import { openDialog } from '@/state/dialogs';
import { getAbacusNotifications } from '@/state/notificationsSelectors';
import { getMarketIds } from '@/state/perpetualsSelectors';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { formatSeconds } from '@/lib/timeUtils';
@@ -175,7 +175,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
useTrigger: ({ trigger }) => {
const stringGetter = useStringGetter();
const { transferNotifications } = useLocalNotifications();
const { selectedNetwork } = useSelectedNetwork();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
useEffect(() => {
for (const transfer of transferNotifications) {
@@ -184,7 +184,7 @@ export const notificationTypes: NotificationTypeConfig[] = [
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
const transferType =
type ?? fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
type ?? fromChainId === selectedDydxChainId
? TransferNotificationTypes.Withdrawal
: TransferNotificationTypes.Deposit;
+12 -6
View File
@@ -4,8 +4,11 @@ import { useSelector } from 'react-redux';
import { DEFAULT_DOCUMENT_TITLE } from '@/constants/routes';
import { getSelectedLocale } from '@/state/localizationSelectors';
import { getCurrentMarketData, getCurrentMarketId } from '@/state/perpetualsSelectors';
import type { RootState } from '@/state/_store';
import {
getCurrentMarketId,
getCurrentMarketMidMarketPrice,
getCurrentMarketOraclePrice,
} from '@/state/perpetualsSelectors';
import { useBreakpoints } from './useBreakpoints';
@@ -13,11 +16,14 @@ export const usePageTitlePriceUpdates = () => {
const selectedLocale = useSelector(getSelectedLocale);
const { isNotTablet } = useBreakpoints();
const id = useSelector(getCurrentMarketId);
const oraclePrice = useSelector((state: RootState) => getCurrentMarketData(state)?.oraclePrice);
const oraclePrice = useSelector(getCurrentMarketOraclePrice);
const orderbookMidMarketPrice = useSelector(getCurrentMarketMidMarketPrice);
const price = orderbookMidMarketPrice ?? oraclePrice;
useEffect(() => {
if (id && oraclePrice && isNotTablet) {
const priceString = oraclePrice.toLocaleString(selectedLocale);
if (id && price && isNotTablet) {
const priceString = price.toLocaleString(selectedLocale);
document.title = `$${priceString} ${id} · ${DEFAULT_DOCUMENT_TITLE}`;
} else {
document.title = DEFAULT_DOCUMENT_TITLE;
@@ -26,5 +32,5 @@ export const usePageTitlePriceUpdates = () => {
return () => {
document.title = DEFAULT_DOCUMENT_TITLE;
};
}, [oraclePrice]);
}, [price]);
};
+1 -1
View File
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork } from '@/constants/networks';
import { useAccounts, useLocalStorage } from '@/hooks';
+1 -1
View File
@@ -28,7 +28,7 @@ import { setSubaccount, setHistoricalPnl, removeUncommittedOrderClientId } from
import { getBalances } from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus';
import { hashFromTx } from '@/lib/hashfromTx';
import { hashFromTx } from '@/lib/txUtils';
import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts';
+14 -10
View File
@@ -1,7 +1,9 @@
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useSelector } from 'react-redux';
import { TOKEN_CONFIG_MAP } from '@/constants/networks';
import { DydxChainAsset } from '@/constants/wallets';
import { useSelectedNetwork } from '@/hooks';
import { getSelectedDydxChainId } from '@/state/appSelectors';
export const useTokenConfigs = (): {
tokensConfigs: {
@@ -10,31 +12,33 @@ export const useTokenConfigs = (): {
name: string;
decimals: number;
gasDenom?: string;
},
};
[DydxChainAsset.CHAINTOKEN]: {
denom: string;
name: string;
decimals: number;
gasDenom?: string;
},
};
};
usdcDenom: string;
usdcDecimals: number;
usdcGasDenom: string;
usdcLabel: string;
chainTokenDenom: string;
chainTokenDecimals: number;
chainTokenLabel: string;
} => {
const { selectedNetwork } = useSelectedNetwork();
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const tokensConfigs = TOKEN_CONFIG_MAP[selectedDydxChainId];
return {
return {
tokensConfigs,
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
usdcGasDenom: tokensConfigs[DydxChainAsset.USDC].gasDenom,
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
};
};
+8 -4
View File
@@ -1,12 +1,15 @@
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useSelector } from 'react-redux';
import { useSelectedNetwork } from '@/hooks';
import { LINKS_CONFIG_MAP } from '@/constants/networks';
import { getSelectedDydxChainId } from '@/state/appSelectors';
const FALLBACK_URL = 'https://help.dydx.exchange/';
export interface LinksConfigs {
tos: string;
privacy: string;
statusPage: string;
mintscan: string;
mintscanBase: string;
feedback?: string;
@@ -27,12 +30,13 @@ export interface LinksConfigs {
}
export const useURLConfigs = (): LinksConfigs => {
const { selectedNetwork } = useSelectedNetwork();
const linksConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links as LinksConfigs;
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const linksConfigs = LINKS_CONFIG_MAP[selectedDydxChainId] as LinksConfigs;
return {
tos: linksConfigs.tos,
privacy: linksConfigs.privacy,
statusPage: linksConfigs.statusPage,
mintscan: linksConfigs.mintscan,
mintscanBase: linksConfigs.mintscanBase,
feedback: linksConfigs.feedback || FALLBACK_URL,
+14 -11
View File
@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
import { EvmDerivedAddresses } from '@/constants/account';
import { STRING_KEYS } from '@/constants/localization';
import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { ENVIRONMENT_CONFIG_MAP, WALLETS_CONFIG_MAP } from '@/constants/networks';
import {
type DydxAddress,
@@ -32,7 +32,7 @@ import {
WalletType as CosmosWalletType,
} from 'graz';
import { getSelectedNetwork } from '@/state/appSelectors';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { resolveWagmiConnector } from '@/lib/wagmi';
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
@@ -91,8 +91,8 @@ export const useWalletConnection = () => {
// Wallet connection
const selectedNetwork = useSelector(getSelectedNetwork);
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const walletConnectConfig = WALLETS_CONFIG_MAP[selectedDydxChainId].walletconnect;
const wagmiConnector = useMemo(
() =>
walletType && walletConnectionType
@@ -163,17 +163,20 @@ export const useWalletConnection = () => {
}
}
} catch (error) {
throw Object.assign(
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
{
walletConnectionType: walletConnection?.type,
}
);
const { isErrorExpected } = parseWalletError({ error, stringGetter });
if (!isErrorExpected) {
throw Object.assign(
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
{
walletConnectionType: walletConnection?.type,
}
);
}
}
return {
walletType,
walletConnectionType: walletConnection.type,
walletConnectionType: walletConnection?.type,
};
},
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
+3 -4
View File
@@ -3,9 +3,9 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
import { AbacusApiStatus } from '@/constants/abacus';
import { ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
import { isDev } from '@/constants/networks';
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
import { useApiState, useStringGetter, useURLConfigs } from '@/hooks';
import { ChatIcon, LinkOutIcon } from '@/icons';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -28,8 +28,7 @@ enum ExchangeStatus {
export const FooterDesktop = () => {
const stringGetter = useStringGetter();
const { height, indexerHeight, status, statusErrorMessage } = useApiState();
const { selectedNetwork } = useSelectedNetwork();
const { statusPage } = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links;
const { statusPage } = useURLConfigs();
const { exchangeStatus, label } =
!status || status === AbacusApiStatus.NORMAL
+7 -5
View File
@@ -34,7 +34,7 @@ import {
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
import { DialogTypes } from '@/constants/dialogs';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
import { DydxNetwork, isTestnet } from '@/constants/networks';
import { RootStore } from '@/state/_store';
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -43,7 +43,8 @@ import { openDialog } from '@/state/dialogs';
import { StatefulOrderError } from '../errors';
import { bytesToBigInt } from '../numbers';
import { log } from '../telemetry';
import { hashFromTx } from '../hashfromTx';
import { hashFromTx, getMintscanTxLink } from '../txUtils';
import { getDydxChainIdFromNetwork } from '../network';
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
private compositeClient: CompositeClient | undefined;
@@ -238,9 +239,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
if (isTestnet) {
console.log(
`${ENVIRONMENT_CONFIG_MAP[
this.compositeClient.network.getString() as DydxNetwork
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
getMintscanTxLink(
getDydxChainIdFromNetwork(this.compositeClient.network.getString() as DydxNetwork),
hash
)
);
} else console.log(`txHash: ${hash}`);
-3
View File
@@ -1,3 +0,0 @@
export const hashFromTx = (
txHash: string | Uint8Array
): string => `0x${Buffer.from(txHash).toString('hex')}`;
+5 -1
View File
@@ -1,4 +1,8 @@
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP, type DydxChainId } from '@/constants/networks';
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
export const getDydxChainIdFromNetwork = (network: DydxNetwork) => {
return ENVIRONMENT_CONFIG_MAP[network].dydxChainId as DydxChainId;
};
+3 -3
View File
@@ -31,13 +31,13 @@ export const fetchSquidStatus = async (
const response = await fetch(url, {
headers: {
"x-integrator-id": integratorId || 'dYdX-api'
'x-integrator-id': integratorId || 'dYdX-api',
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error);
throw error;
}
return response.json();
@@ -45,4 +45,4 @@ export const fetchSquidStatus = async (
export const getNobleChainId = () => {
return isMainnet ? 'noble-1' : 'grand-1';
}
};
+11 -8
View File
@@ -1,8 +1,10 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
import { THEME_NAMES } from '@/constants/styles/colors';
import type { ChartLineType } from '@/constants/tvchart';
import { AppTheme, type AppColorMode } from '@/state/configs';
import { type AppColorMode, AppTheme } from '@/state/configs';
import { Themes } from '@/styles/themes';
@@ -49,23 +51,24 @@ export const getHistorySlice = ({
return bars.filter(({ time }) => time >= fromMs);
};
export const getOrderLineColors = ({
export const getChartLineColors = ({
appTheme,
appColorMode,
side,
chartLineType,
}: {
appTheme: AppTheme;
appColorMode: AppColorMode;
side: OrderSide;
chartLineType: ChartLineType;
}) => {
const theme = Themes[appTheme][appColorMode];
const orderColor = {
const orderColors = {
[OrderSide.BUY]: theme.positive,
[OrderSide.SELL]: theme.negative,
}[side];
['position']: null,
};
return {
orderColor,
maybeQuantityColor: orderColors[chartLineType],
borderColor: theme.borderDefault,
backgroundColor: theme.layer1,
textColor: theme.textTertiary,
@@ -83,7 +86,7 @@ export const getWidgetOverrides = ({
const theme = Themes[appTheme][appColorMode];
return {
theme: appTheme === AppTheme.Dark ? 'dark' : AppTheme.Light ? 'light' : '',
theme: THEME_NAMES[appTheme],
overrides: {
'paneProperties.background': theme.layer2,
'paneProperties.horzGridProperties.color': theme.layer3,
+7
View File
@@ -0,0 +1,7 @@
import { DydxChainId, LINKS_CONFIG_MAP } from '@/constants/networks';
export const hashFromTx = (txHash: string | Uint8Array): string =>
`0x${Buffer.from(txHash).toString('hex')}`;
export const getMintscanTxLink = (dydxChainId: DydxChainId, txHash: string): string =>
`${LINKS_CONFIG_MAP[dydxChainId]?.mintscan?.replace('{tx_hash}', txHash.toString())}`;
+11 -1
View File
@@ -92,6 +92,10 @@ export const getWalletErrorType = ({ error }: { error: Error }) => {
return WalletErrorType.ChainMismatch;
}
if (messageLower.includes('Missing or invalid. request() method: wallet_switchEthereumChain')) {
return WalletErrorType.SwitchChainMethodMissing;
}
// ImToken - User canceled
if (messageLower.includes('用户取消了操作')) {
return WalletErrorType.UserCanceled;
@@ -113,13 +117,18 @@ export const parseWalletError = ({
}) => {
const walletErrorType = getWalletErrorType({ error });
let message;
let isErrorExpected;
switch (walletErrorType) {
case WalletErrorType.ChainMismatch:
case WalletErrorType.UserCanceled: {
case WalletErrorType.UserCanceled:
case WalletErrorType.SwitchChainMethodMissing: {
isErrorExpected = true;
message = error.message;
break;
}
default: {
isErrorExpected = false;
message = stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: {
@@ -132,5 +141,6 @@ export const parseWalletError = ({
return {
walletErrorType,
message,
isErrorExpected,
};
};
+24 -1
View File
@@ -9,10 +9,13 @@ import {
HistoricalTradingRewardsPeriods,
} from '@/constants/abacus';
import { isMainnet } from '@/constants/networks';
import { STRING_KEYS } from '@/constants/localization';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { Output, OutputType } from '@/components/Output';
import { Panel } from '@/components/Panel';
import { ToggleGroup } from '@/components/ToggleGroup';
import { WithTooltip } from '@/components/WithTooltip';
@@ -20,6 +23,9 @@ import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTa
import abacusStateManager from '@/lib/abacus';
// TODO: set in env featureFlag config
const REWARDS_HISTORY_START_DATE_MS = isMainnet ? 1706486400000 : 1704844800000;
export const RewardHistoryPanel = () => {
const stringGetter = useStringGetter();
@@ -47,7 +53,20 @@ export const RewardHistoryPanel = () => {
<WithTooltip tooltip="reward-history">
<h3>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}</h3>
</WithTooltip>
<span>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION })}</span>
<span>
{stringGetter({
key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION,
params: {
REWARDS_HISTORY_START_DATE: (
<Styled.Output
type={OutputType.Date}
value={REWARDS_HISTORY_START_DATE_MS}
timeOptions={{ useUTC: true }}
/>
),
},
})}
</span>
</Styled.Title>
<ToggleGroup
items={[
@@ -103,3 +122,7 @@ Styled.Content = styled.div`
${layoutMixins.flexColumn}
gap: 0.75rem;
`;
Styled.Output = styled(Output)`
display: inline;
`;
+6 -3
View File
@@ -1,6 +1,7 @@
import styled, { AnyStyledComponent, css } from 'styled-components';
import { useNavigate } from 'react-router-dom';
import { isMainnet } from '@/constants/networks';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute } from '@/constants/routes';
@@ -27,6 +28,8 @@ const RewardsPage = () => {
const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
const showTradingRewards = testFlags.showTradingRewards || !isMainnet;
return (
<div>
{isTablet && (
@@ -37,7 +40,7 @@ const RewardsPage = () => {
)}
<DetachedSection>
<Styled.GridLayout
showTradingRewards={testFlags.showTradingRewards}
showTradingRewards={showTradingRewards}
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
@@ -51,7 +54,7 @@ const RewardsPage = () => {
</>
)}
{testFlags.showTradingRewards && (
{showTradingRewards && (
<Styled.TradingRewardsColumn>
<TradingRewardsSummaryPanel />
{isTablet && <RewardsHelpPanel />}
@@ -60,7 +63,7 @@ const RewardsPage = () => {
)}
{isNotTablet && (
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
<Styled.OtherColumn showTradingRewards={showTradingRewards}>
<RewardsHelpPanel />
</Styled.OtherColumn>
)}
+4
View File
@@ -1,4 +1,8 @@
import { DydxChainId, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import type { RootState } from './_store';
export const getApiState = (state: RootState) => state.app.apiState;
export const getSelectedNetwork = (state: RootState) => state.app.selectedNetwork;
export const getSelectedDydxChainId = (state: RootState) =>
ENVIRONMENT_CONFIG_MAP[state.app.selectedNetwork].dydxChainId as DydxChainId;
+6
View File
@@ -107,6 +107,12 @@ export const getCurrentMarketHistoricalFundings = createSelector(
currentMarketId ? historicalFundings?.[currentMarketId] ?? [] : []
);
/**
* @returns oracle price of the market the user is currently viewing
*/
export const getCurrentMarketOraclePrice = (state: RootState) =>
getCurrentMarketData(state)?.oraclePrice;
/**
* @returns Mid market price for the market the user is currently viewing
*/
+19 -184
View File
@@ -1,205 +1,40 @@
import { useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import type {
IChartingLibraryWidget,
IOrderLineAdapter,
ResolutionString,
} from 'public/tradingview/charting_library';
import type { ResolutionString } from 'public/tradingview/charting_library';
import { AbacusOrderStatus } from '@/constants/abacus';
import { DEFAULT_RESOLUTION, RESOLUTION_CHART_CONFIGS } from '@/constants/candles';
import { DEFAULT_MARKETID } from '@/constants/markets';
import { type OrderType, ORDER_TYPE_STRINGS } from '@/constants/trade';
import type { TvWidget } from '@/constants/tvchart';
import { useStringGetter } from '@/hooks';
import { useTradingView, useTradingViewTheme } from '@/hooks/tradingView';
import {
useChartLines,
useChartMarketAndResolution,
useTradingView,
useTradingViewTheme,
} from '@/hooks/tradingView';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { getCurrentMarketOrders } from '@/state/accountSelectors';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { setTvChartResolution } from '@/state/perpetuals';
import { getCurrentMarketId, getSelectedResolutionForMarket } from '@/state/perpetualsSelectors';
import { layoutMixins } from '@/styles/layoutMixins';
import { MustBigNumber } from '@/lib/numbers';
import { getOrderLineColors } from '@/lib/tradingView/utils';
type TvWidget = IChartingLibraryWidget & { _id?: string; _ready?: boolean };
let orderLines: Record<string, IOrderLineAdapter> = {};
export const TvChart = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const [isChartReady, setIsChartReady] = useState(false);
const [showOrderLines, setShowOrderLines] = useState(false);
const displayButtonRef = useRef<HTMLElement | null>(null);
const appTheme = useSelector(getAppTheme);
const appColorMode = useSelector(getAppColorMode);
const currentMarketId: string = useSelector(getCurrentMarketId) || DEFAULT_MARKETID;
const currentMarketOrders = useSelector(getCurrentMarketOrders, shallowEqual);
const selectedResolution: string =
useSelector(getSelectedResolutionForMarket(currentMarketId)) || DEFAULT_RESOLUTION;
const tvWidgetRef = useRef<TvWidget | null>(null);
const tvWidget = tvWidgetRef.current;
const isWidgetReady = tvWidget?._ready;
const chart = isWidgetReady ? tvWidget?.chart() : undefined;
const chartResolution = chart?.resolution?.();
const displayButtonRef = useRef<HTMLElement | null>(null);
const displayButton = displayButtonRef.current;
const { savedResolution } = useTradingView({ tvWidgetRef, displayButtonRef, setIsChartReady });
useTradingViewTheme({ tvWidget, isWidgetReady, orderLines });
const setVisibleRangeForResolution = ({ resolution }: { resolution: ResolutionString }) => {
// Different resolutions have different timeframes to display data efficiently.
const { defaultRange } = RESOLUTION_CHART_CONFIGS[resolution];
// from/to values converted to epoch seconds
const newRange = {
from: (Date.now() - defaultRange) / 1000,
to: Date.now() / 1000,
};
tvWidget?.activeChart().setVisibleRange(newRange, { percentRightMargin: 10 });
};
/**
* @description Hook to handle changing chart resolution
*/
useEffect(() => {
if (chartResolution) {
if (chartResolution !== selectedResolution) {
dispatch(setTvChartResolution({ marketId: currentMarketId, resolution: chartResolution }));
}
setVisibleRangeForResolution({ resolution: chartResolution });
}
}, [chartResolution]);
/**
* @description Hook to handle changing markets
*/
useEffect(() => {
if (currentMarketId && isWidgetReady) {
const resolution = savedResolution || selectedResolution;
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
}
}, [currentMarketId, isWidgetReady]);
/**
* @description Hook to handle order line toggle state
*/
useEffect(() => {
if (isChartReady && displayButtonRef && displayButtonRef.current) {
displayButtonRef.current.onclick = () => {
const newShowOrderLinesState = !showOrderLines;
if (newShowOrderLinesState) {
displayButtonRef.current?.classList?.add('order-lines-active');
} else {
displayButtonRef.current?.classList?.remove('order-lines-active');
}
setShowOrderLines(newShowOrderLinesState);
};
}
}, [isChartReady, showOrderLines]);
/**
* @description Hook to handle drawing order lines
*/
useEffect(() => {
if (tvWidget && isChartReady) {
tvWidget.onChartReady(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
} else {
deleteOrderLines();
}
});
});
}
}, [isChartReady, showOrderLines, currentMarketOrders, currentMarketId]);
const drawOrderLines = () => {
if (!currentMarketOrders) return;
currentMarketOrders.forEach(
({
id,
type,
status,
side,
cancelReason,
remainingSize,
size,
triggerPrice,
price,
trailingPercent,
}) => {
const key = `${side.rawValue}-${id}`;
const quantity = (remainingSize ?? size).toString();
const orderType = type.rawValue as OrderType;
const orderLabel = stringGetter({
key: ORDER_TYPE_STRINGS[orderType].orderTypeKey,
});
const orderString = trailingPercent ? `${orderLabel} ${trailingPercent}%` : orderLabel;
const shouldShow =
!cancelReason &&
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
const maybeOrderLine = key in orderLines ? orderLines[key] : null;
if (maybeOrderLine) {
if (!shouldShow) {
maybeOrderLine.remove();
delete orderLines[key];
return;
} else if (maybeOrderLine.getQuantity() !== quantity) {
maybeOrderLine.setQuantity(quantity);
return;
}
} else if (shouldShow) {
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
getOrderLineColors({ side: side.rawValue, appTheme, appColorMode });
const orderLine = tvWidget
?.chart()
.createOrderLine({ disableUndo: false })
.setPrice(MustBigNumber(triggerPrice ?? price).toNumber())
.setQuantity(quantity)
.setText(orderString)
.setLineColor(orderColor)
.setQuantityBackgroundColor(orderColor)
.setQuantityBorderColor(borderColor)
.setBodyBackgroundColor(backgroundColor)
.setBodyBorderColor(borderColor)
.setBodyTextColor(textColor)
.setQuantityTextColor(textButtonColor);
if (orderLine) {
orderLines[key] = orderLine;
}
}
}
);
};
const deleteOrderLines = () => {
Object.values(orderLines).forEach((line) => {
line.remove();
});
orderLines = {};
};
useChartMarketAndResolution({
tvWidget,
isWidgetReady,
savedResolution: savedResolution as ResolutionString | undefined,
});
const { chartLines } = useChartLines({ tvWidget, displayButton, isChartReady });
useTradingViewTheme({ tvWidget, isWidgetReady, chartLines });
return (
<Styled.PriceChart isChartReady={isChartReady}>
@@ -32,7 +32,15 @@ export const ExternalNavKeplrDialog = ({ setIsOpen }: ElementProps) => {
openDialog({
type: DialogTypes.ExternalLink,
dialogProps: {
buttonText: stringGetter({ key: STRING_KEYS.CONTINUE }),
link: keplrDashboard,
title: stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_STAKING_GOVERNANCE }),
slotContent: stringGetter({
key: STRING_KEYS.STAKE_WITH_KEPLR_AND_LEAVING_DESCRIPTION,
params: {
CTA: stringGetter({ key: STRING_KEYS.CONTINUE }),
},
}),
},
})
);
@@ -25,7 +25,7 @@ import { Switch } from '@/components/Switch';
import { WithReceipt } from '@/components/WithReceipt';
import { WithTooltip } from '@/components/WithTooltip';
import { getSelectedNetwork } from '@/state/appSelectors';
import { getSelectedNetwork, getSelectedDydxChainId } from '@/state/appSelectors';
import { track } from '@/lib/analytics';
import { isTruthy } from '@/lib/isTruthy';
@@ -44,7 +44,6 @@ export const GenerateKeys = ({
onKeysDerived = () => {},
}: ElementProps) => {
const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints();
const [shouldRememberMe, setShouldRememberMe] = useState(false);
@@ -66,17 +65,30 @@ export const GenerateKeys = ({
try {
await matchNetwork?.();
return true;
} catch (error) {
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
const { message, walletErrorType, isErrorExpected } = parseWalletError({
error,
stringGetter,
});
if (!isErrorExpected) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
}
if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message);
throw error;
}
return false;
}
};
const switchNetworkAndDeriveKeys = async () => {
const networkSwitched = await switchNetwork();
if (networkSwitched) await deriveKeys();
};
// 2. Derive keys from EVM account
const { getWalletFromEvmSignature } = useDydxClient();
const { getSubaccounts } = useAccounts();
@@ -86,7 +98,8 @@ export const GenerateKeys = ({
EvmDerivedAccountStatus.Derived,
].includes(status);
const signTypedData = getSignTypedData(selectedNetwork);
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const signTypedData = getSignTypedData(selectedDydxChainId);
const { signTypedDataAsync } = useSignTypedData({
...signTypedData,
domain: {
@@ -154,11 +167,16 @@ export const GenerateKeys = ({
setStatus(EvmDerivedAccountStatus.Derived);
} catch (error) {
setStatus(EvmDerivedAccountStatus.NotDerived);
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
const { message, walletErrorType, isErrorExpected } = parseWalletError({
error,
stringGetter,
});
if (message) {
setError(message);
log('GenerateKeys/deriveKeys', error, { walletErrorType });
if (!isErrorExpected) {
log('GenerateKeys/deriveKeys', error, { walletErrorType });
}
}
}
};
@@ -231,7 +249,7 @@ export const GenerateKeys = ({
{!isMatchingNetwork ? (
<Button
action={ButtonAction.Primary}
onClick={() => switchNetwork().then(deriveKeys).then(onKeysDerived)}
onClick={() => switchNetworkAndDeriveKeys().then(onKeysDerived)}
state={{ isLoading: isSwitchingNetwork }}
>
{stringGetter({ key: STRING_KEYS.SWITCH_NETWORK })}
@@ -10,7 +10,7 @@ import { TransferInputField, TransferInputTokenResource, TransferType } from '@/
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isMainnet } from '@/constants/networks';
import { isMainnet } from '@/constants/networks';
import { MAX_CCTP_TRANSFER_AMOUNT, MAX_PRICE_IMPACT, NumberSign } from '@/constants/numbers';
import type { EvmAddress } from '@/constants/wallets';
@@ -32,6 +32,7 @@ import { OutputType } from '@/components/Output';
import { Tag } from '@/components/Tag';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
@@ -56,7 +57,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [requireUserActionInWallet, setRequireUserActionInWallet] = useState(false);
const { selectedNetwork } = useSelectedNetwork();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const { evmAddress, signerWagmi, publicClientWagmi, nobleAddress } = useAccounts();
@@ -262,9 +263,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
if (txHash) {
addTransferNotification({
txHash: txHash,
toChainId: !isCctp
? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
: getNobleChainId(),
toChainId: !isCctp ? selectedDydxChainId : getNobleChainId(),
fromChainId: chainIdStr || undefined,
toAmount: summary?.usdcSize || undefined,
triggeredAt: Date.now(),
@@ -9,9 +9,14 @@ import { TransferInputField, TransferInputTokenResource, TransferType } from '@/
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isMainnet } from '@/constants/networks';
import { isMainnet } from '@/constants/networks';
import { TransferNotificationTypes } from '@/constants/notifications';
import { MAX_CCTP_TRANSFER_AMOUNT, MAX_PRICE_IMPACT, NumberSign } from '@/constants/numbers';
import {
MAX_CCTP_TRANSFER_AMOUNT,
MAX_PRICE_IMPACT,
MIN_CCTP_TRANSFER_AMOUNT,
NumberSign,
} from '@/constants/numbers';
import {
useAccounts,
@@ -40,6 +45,7 @@ import { Icon, IconName } from '@/components/Icon';
import { SourceSelectMenu } from '@/views/forms/AccountManagementForms/SourceSelectMenu';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { getSubaccount } from '@/state/accountSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
@@ -54,7 +60,7 @@ export const WithdrawForm = () => {
const stringGetter = useStringGetter();
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const { selectedNetwork } = useSelectedNetwork();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const { sendSquidWithdraw } = useSubaccount();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
@@ -177,9 +183,7 @@ export const WithdrawForm = () => {
addTransferNotification({
txHash: txHash,
type: TransferNotificationTypes.Withdrawal,
fromChainId: !isCctp
? ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
: getNobleChainId(),
fromChainId: !isCctp ? selectedDydxChainId : getNobleChainId(),
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
@@ -329,6 +333,12 @@ export const WithdrawForm = () => {
},
});
}
if (
!debouncedAmountBN.isZero() &&
MustBigNumber(debouncedAmountBN).lte(MIN_CCTP_TRANSFER_AMOUNT)
) {
return 'Amount must be greater than 10 USDC';
}
}
if (isMainnet && MustBigNumber(summary?.aggregatePriceImpact).gte(MAX_PRICE_IMPACT)) {
+7 -2
View File
@@ -14,6 +14,7 @@ import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/b
import { TOKEN_DECIMALS } from '@/constants/numbers';
import { STRING_KEYS } from '@/constants/localization';
import { MobilePlaceOrderSteps } from '@/constants/trade';
import { useBreakpoints, useIsFirstRender, useStringGetter, useSubaccount } from '@/hooks';
import { useOnLastOrderIndexed } from '@/hooks/useOnLastOrderIndexed';
@@ -35,7 +36,6 @@ import { Orderbook, orderbookMixins, type OrderbookScrollBehavior } from '@/view
import { PositionPreview } from '@/views/forms/TradeForm/PositionPreview';
import { getCurrentMarketPositionData } from '@/state/accountSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getClosePositionInputErrors, getInputClosePositionData } from '@/state/inputsSelectors';
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
@@ -291,9 +291,14 @@ export const ClosePositionForm = ({
isLoading={isClosingPosition}
hasValidationErrors={hasInputErrors}
actionStringKey={inputAlert?.actionStringKey}
validationErrorString={alertContent}
summary={summary ?? undefined}
currentStep={currentStep}
isClosePosition
confirmButtonConfig={{
stringKey: STRING_KEYS.CLOSE_ORDER,
buttonTextStringKey: STRING_KEYS.CLOSE_POSITION,
buttonAction: ButtonAction.Destroy,
}}
/>
</Styled.Footer>
</Styled.ClosePositionForm>
+41 -5
View File
@@ -1,8 +1,10 @@
import { type FormEvent, useState, Ref, useCallback, useEffect } from 'react';
import { type FormEvent, useState, Ref, useCallback } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { NumberFormatValues, SourceInfo } from 'react-number-format';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { AlertType } from '@/constants/alerts';
import {
@@ -16,7 +18,12 @@ import {
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { USD_DECIMALS } from '@/constants/numbers';
import { InputErrorData, TradeBoxKeys, MobilePlaceOrderSteps } from '@/constants/trade';
import {
InputErrorData,
TradeBoxKeys,
MobilePlaceOrderSteps,
ORDER_TYPE_STRINGS,
} from '@/constants/trade';
import { breakpoints } from '@/styles';
import { useStringGetter, useSubaccount } from '@/hooks';
@@ -37,11 +44,16 @@ import { WithTooltip } from '@/components/WithTooltip';
import { Orderbook } from '@/views/tables/Orderbook';
import { setTradeFormInputs } from '@/state/inputs';
import { getCurrentInput, getTradeFormInputs, useTradeFormData } from '@/state/inputsSelectors';
import {
getCurrentInput,
getInputTradeData,
getTradeFormInputs,
useTradeFormData,
} from '@/state/inputsSelectors';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import abacusStateManager from '@/lib/abacus';
import { getTradeInputAlert } from '@/lib/tradeData';
import { getSelectedOrderSide, getSelectedTradeType, getTradeInputAlert } from '@/lib/tradeData';
import { AdvancedTradeOptions } from './TradeForm/AdvancedTradeOptions';
import { TradeSizeInputs } from './TradeForm/TradeSizeInputs';
@@ -108,8 +120,21 @@ export const TradeForm = ({
const tradeFormInputValues = useSelector(getTradeFormInputs, shallowEqual);
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
const currentTradeData = useSelector(getInputTradeData, shallowEqual);
const { side, type } = currentTradeData || {};
const selectedTradeType = getSelectedTradeType(type);
const selectedOrderSide = getSelectedOrderSide(side);
const needsAdvancedOptions =
needsGoodUntil || timeInForceOptions || executionOptions || (needsPostOnly || postOnlyTooltip) || (needsReduceOnly || reduceOnlyTooltip);
needsGoodUntil ||
timeInForceOptions ||
executionOptions ||
needsPostOnly ||
postOnlyTooltip ||
needsReduceOnly ||
reduceOnlyTooltip;
const tradeFormInputs: TradeBoxInputConfig[] = [];
@@ -139,6 +164,11 @@ export const TradeForm = ({
alertType = inputAlert?.type;
}
const orderSideAction = {
[OrderSide.BUY]: ButtonAction.Create,
[OrderSide.SELL]: ButtonAction.Destroy,
}[selectedOrderSide];
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
@@ -319,9 +349,15 @@ export const TradeForm = ({
isLoading={isPlacingOrder}
hasValidationErrors={hasInputErrors}
actionStringKey={inputAlert?.actionStringKey}
validationErrorString={alertContent}
summary={summary ?? undefined}
currentStep={currentStep}
showDeposit={inputAlert?.errorAction === TradeInputErrorAction.DEPOSIT}
confirmButtonConfig={{
stringKey: ORDER_TYPE_STRINGS[selectedTradeType].orderTypeKey,
buttonTextStringKey: STRING_KEYS.PLACE_ORDER,
buttonAction: orderSideAction,
}}
/>
</Styled.Footer>
</Styled.TradeForm>
@@ -1,16 +1,17 @@
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { useDispatch, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import type { TradeInputSummary } from '@/constants/abacus';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { ORDER_TYPE_STRINGS, MobilePlaceOrderSteps } from '@/constants/trade';
import { MobilePlaceOrderSteps } from '@/constants/trade';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { WithTooltip } from '@/components/WithTooltip';
@@ -20,30 +21,34 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccountId } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { getCurrentInput, getInputTradeData } from '@/state/inputsSelectors';
import { getCurrentInput } from '@/state/inputsSelectors';
import { getSelectedOrderSide, getSelectedTradeType } from '@/lib/tradeData';
type ConfirmButtonConfig = {
stringKey: string;
buttonTextStringKey: string;
buttonAction: ButtonAction;
};
type ElementProps = {
isLoading: boolean;
isClosePosition?: boolean;
actionStringKey?: string;
summary?: TradeInputSummary;
hasValidationErrors?: boolean;
validationErrorString?: string;
currentStep?: MobilePlaceOrderSteps;
showDeposit?: boolean;
showConnectWallet?: boolean;
confirmButtonConfig: ConfirmButtonConfig;
};
export const PlaceOrderButtonAndReceipt = ({
isLoading,
isClosePosition,
actionStringKey,
summary,
hasValidationErrors,
validationErrorString,
currentStep,
showDeposit,
showConnectWallet,
confirmButtonConfig,
}: ElementProps) => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
@@ -52,18 +57,12 @@ export const PlaceOrderButtonAndReceipt = ({
const canAccountTrade = useSelector(calculateCanAccountTrade);
const subaccountNumber = useSelector(getSubaccountId);
const currentInput = useSelector(getCurrentInput);
const currentTradeData = useSelector(getInputTradeData, shallowEqual);
const hasMissingData = subaccountNumber === undefined;
const shouldEnableTrade =
canAccountTrade && !hasMissingData && !hasValidationErrors && currentInput !== 'transfer';
const { side, type } = currentTradeData || {};
const selectedTradeType = getSelectedTradeType(type);
const selectedOrderSide = getSelectedOrderSide(side);
const { fee, price: expectedPrice, total, reward } = summary || {};
const items = [
@@ -110,11 +109,6 @@ export const PlaceOrderButtonAndReceipt = ({
},
];
const orderSideAction = {
[OrderSide.BUY]: ButtonAction.Create,
[OrderSide.SELL]: ButtonAction.Destroy,
}[selectedOrderSide];
const buttonStatesPerStep = {
[MobilePlaceOrderSteps.EditOrder]: {
buttonTextStringKey: shouldEnableTrade
@@ -128,7 +122,7 @@ export const PlaceOrderButtonAndReceipt = ({
[MobilePlaceOrderSteps.PreviewOrder]: {
buttonTextStringKey: STRING_KEYS.CONFIRM_ORDER,
buttonAction: isClosePosition ? ButtonAction.Destroy : orderSideAction,
buttonAction: confirmButtonConfig.buttonAction,
buttonState: { isLoading },
},
[MobilePlaceOrderSteps.PlacingOrder]: {
@@ -145,15 +139,13 @@ export const PlaceOrderButtonAndReceipt = ({
const buttonAction = currentStep
? buttonStatesPerStep[currentStep].buttonAction
: isClosePosition
? ButtonAction.Destroy
: orderSideAction;
: confirmButtonConfig.buttonAction;
let buttonTextStringKey = STRING_KEYS.UNAVAILABLE;
if (currentStep) {
buttonTextStringKey = buttonStatesPerStep[currentStep].buttonTextStringKey;
} else if (shouldEnableTrade) {
buttonTextStringKey = isClosePosition ? STRING_KEYS.CLOSE_POSITION : STRING_KEYS.PLACE_ORDER;
buttonTextStringKey = confirmButtonConfig.buttonTextStringKey;
} else if (actionStringKey) {
buttonTextStringKey = actionStringKey;
}
@@ -165,31 +157,56 @@ export const PlaceOrderButtonAndReceipt = ({
isLoading: isLoading || hasMissingData,
};
const depositButton = (
<Button
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
>
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
</Button>
);
const submitButton = (
<Styled.Button
state={buttonState}
type={ButtonType.Submit}
action={buttonAction}
slotLeft={
hasValidationErrors ? <Styled.WarningIcon iconName={IconName.Warning} /> : undefined
}
>
{stringGetter({
key: buttonTextStringKey,
params: {
ORDER: stringGetter({
key: confirmButtonConfig.stringKey,
}),
},
})}
</Styled.Button>
);
return (
<WithDetailsReceipt detailItems={items}>
{!canAccountTrade || showConnectWallet ? (
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} />
) : showDeposit ? (
<Button
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
>
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
</Button>
depositButton
) : (
<Button state={buttonState} type={ButtonType.Submit} action={buttonAction}>
{stringGetter({
key: buttonTextStringKey,
params: {
ORDER: stringGetter({
key: isClosePosition
? STRING_KEYS.CLOSE_ORDER
: ORDER_TYPE_STRINGS[selectedTradeType].orderTypeKey,
}),
},
})}
</Button>
<WithTooltip tooltipString={hasValidationErrors ? validationErrorString : undefined}>
{submitButton}
</WithTooltip>
)}
</WithDetailsReceipt>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Button = styled(Button)`
width: 100%;
`;
Styled.WarningIcon = styled(Icon)`
color: var(--color-warning);
`;
+4 -5
View File
@@ -9,7 +9,6 @@ import { TransferInputField, TransferType } from '@/constants/abacus';
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 } from '@/constants/numbers';
import { DydxChainAsset } from '@/constants/wallets';
@@ -18,7 +17,6 @@ import {
useAccounts,
useDydxClient,
useRestrictions,
useSelectedNetwork,
useStringGetter,
useSubaccount,
useTokenConfigs,
@@ -41,6 +39,7 @@ import { TransferButtonAndReceipt } from '@/views/forms/TransferForm/TransferBut
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { getSubaccount } from '@/state/accountSelectors';
import { getSelectedDydxChainId } from '@/state/appSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
@@ -63,7 +62,7 @@ export const TransferForm = ({
const { dydxAddress } = useAccounts();
const { transfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
const {
@@ -239,7 +238,7 @@ export const TransferForm = ({
const networkOptions = [
{
chainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
chainId: selectedDydxChainId,
label: (
<Styled.InlineRow>
<AssetIcon symbol="DYDX" /> {stringGetter({ key: STRING_KEYS.DYDX_CHAIN })}
@@ -322,7 +321,7 @@ export const TransferForm = ({
/>
<Styled.NetworkSelectMenu
label={stringGetter({ key: STRING_KEYS.NETWORK })}
value={ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId}
value={selectedDydxChainId}
slotTriggerAfter={null}
>
{networkOptions.map(({ chainId, label }) => (
@@ -1,18 +1,21 @@
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import styled, { css, type AnyStyledComponent } from 'styled-components';
import { StatusResponse } from '@0xsquid/sdk';
import { useStringGetter, useSelectedNetwork, useURLConfigs } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
import { TransferNotificationTypes } from '@/constants/notifications';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Link } from '@/components/Link';
import { Icon, IconName } from '@/components/Icon';
import { LoadingDots } from '@/components/Loading/LoadingDots';
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { TransferNotificationTypes } from '@/constants/notifications';
import { getSelectedDydxChainId } from '@/state/appSelectors';
type ElementProps = {
status?: StatusResponse;
@@ -32,9 +35,8 @@ enum TransferStatusStep {
export const TransferStatusSteps = ({ className, status, type }: ElementProps & StyleProps) => {
const stringGetter = useStringGetter();
const { selectedNetwork } = useSelectedNetwork();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const { mintscan: mintscanTxUrl } = useURLConfigs();
const dydxChainId = ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId;
const { currentStep, steps } = useMemo(() => {
const routeStatus = status?.routeStatus;
@@ -55,7 +57,7 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
link:
type === TransferNotificationTypes.Deposit
? status?.fromChain?.transactionUrl
: routeStatus?.[0]?.chainId === dydxChainId && routeStatus[0].txHash
: routeStatus?.[0]?.chainId === selectedDydxChainId && routeStatus[0].txHash
? `${mintscanTxUrl?.replace('{tx_hash}', routeStatus[0].txHash.replace('0x', ''))}`
: undefined,
},
@@ -81,7 +83,7 @@ export const TransferStatusSteps = ({ className, status, type }: ElementProps &
link:
type === TransferNotificationTypes.Withdrawal
? status?.toChain?.transactionUrl
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
: currentStatus?.chainId === selectedDydxChainId && currentStatus?.txHash
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash.replace('0x', ''))}`
: undefined,
},