Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59a276e36b | ||
|
|
5531e21c76 | ||
|
|
4ebeb21841 | ||
|
|
b638ec1ad6 | ||
|
|
f7a1cfc67e | ||
|
|
d66a776ad2 | ||
|
|
e3e00f4f1e | ||
|
|
9ea578b8e4 | ||
|
|
f47fda45bf |
+1
-1
@@ -42,7 +42,7 @@
|
||||
"@cosmjs/tendermint-rpc": "^0.32.1",
|
||||
"@dydxprotocol/v4-abacus": "^1.4.2",
|
||||
"@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",
|
||||
|
||||
Generated
+4
-4
@@ -36,8 +36,8 @@ dependencies:
|
||||
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
|
||||
@@ -1323,8 +1323,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:
|
||||
|
||||
@@ -0,0 +1,635 @@
|
||||
{
|
||||
"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",
|
||||
"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",
|
||||
"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]",
|
||||
"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 V4"
|
||||
},
|
||||
"[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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,9 @@ TagStory.argTypes = {
|
||||
control: { type: 'select' },
|
||||
defaultValue: undefined,
|
||||
},
|
||||
isHighlighted: {
|
||||
options: [true, false],
|
||||
control: { type: 'select' },
|
||||
defaultValue: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,5 +76,6 @@ export const Tag = styled.span<StyleProps>`
|
||||
isHighlighted &&
|
||||
css`
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-text-button);
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -10,7 +10,6 @@ export enum DialogTypes {
|
||||
Help = 'Help',
|
||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||
MnemonicExport = 'MnemonicExport',
|
||||
MobileDownload = 'MobileDownload',
|
||||
MobileSignIn = 'MobileSignIn',
|
||||
Onboarding = 'Onboarding',
|
||||
OrderDetails = 'OrderDetails',
|
||||
|
||||
@@ -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%
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -40,6 +40,7 @@ export enum WalletErrorType {
|
||||
// General
|
||||
ChainMismatch,
|
||||
UserCanceled,
|
||||
SwitchChainMethodMissing,
|
||||
|
||||
// Non-Deterministic
|
||||
NonDeterministicWallet,
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { useChartLines } from './useChartLines';
|
||||
export { useChartMarketAndResolution } from './useChartMarketAndResolution';
|
||||
export { useTradingView } from './useTradingView';
|
||||
export { useTradingViewTheme } from './useTradingViewTheme';
|
||||
|
||||
@@ -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,65 @@
|
||||
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?.();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentMarketId && isWidgetReady) {
|
||||
const resolution = savedResolution || selectedResolution;
|
||||
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
|
||||
}
|
||||
}, [currentMarketId, isWidgetReady, savedResolution, selectedResolution]);
|
||||
|
||||
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 });
|
||||
};
|
||||
};
|
||||
@@ -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', () =>
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -62,7 +62,6 @@ export const DialogManager = () => {
|
||||
[DialogTypes.ExternalLink]: <ExternalLinkDialog {...modalProps} />,
|
||||
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
|
||||
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
||||
[DialogTypes.MobileDownload]: <MobilDownloadDialog {...modalProps} />,
|
||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+11
-1
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
@@ -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 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { AES } from 'crypto-js';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { AlertMessage } from '@/components/AlertMessage';
|
||||
import { Dialog } from '@/components/Dialog';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { ToggleButton } from '@/components/ToggleButton';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
type ElementProps = {
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
|
||||
const MobileQrCode = ({
|
||||
}: {
|
||||
}) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const appLink = 'https://apps.apple.com/app/id6475599596';
|
||||
|
||||
return (
|
||||
<Styled.QrCodeContainer isShowing={true}>
|
||||
<QrCode hasLogo size={432} value={appLink} />
|
||||
<span>{stringGetter({ key: STRING_KEYS.CLICK_TO_SHOW })}</span>
|
||||
</Styled.QrCodeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileDownloadDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<p>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.WHILE_ONBOARDING,
|
||||
params: {
|
||||
SYNC_WITH_DESKTOP: (
|
||||
<strong>{stringGetter({ key: STRING_KEYS.SYNC_WITH_DESKTOP })}</strong>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
<MobileQrCode
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog isOpen setIsOpen={setIsOpen} title={'Download mobile app'}>
|
||||
<Styled.Content>{content}</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Content = styled.div`
|
||||
${layoutMixins.column}
|
||||
gap: 1rem;
|
||||
|
||||
strong {
|
||||
font-weight: 900;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
footer {
|
||||
${layoutMixins.row}
|
||||
justify-content: space-between;
|
||||
|
||||
svg {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.WaitingSpan = styled.span`
|
||||
strong {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.QrCodeContainer = styled.figure<{ isShowing: boolean }>`
|
||||
${layoutMixins.stack}
|
||||
|
||||
overflow: hidden;
|
||||
border-radius: 0.75em;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: 0.2s;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(var(--hover-filter-base));
|
||||
}
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
transition: 0.16s;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
pointer-events: none;
|
||||
|
||||
${({ isShowing }) =>
|
||||
!isShowing &&
|
||||
css`
|
||||
filter: blur(1rem) brightness(1.4);
|
||||
will-change: filter;
|
||||
`}
|
||||
}
|
||||
|
||||
> span {
|
||||
place-self: center;
|
||||
|
||||
font-size: 1.4em;
|
||||
color: var(--color-text-2);
|
||||
|
||||
${({ isShowing }) =>
|
||||
isShowing &&
|
||||
css`
|
||||
opacity: 0;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
@@ -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();
|
||||
@@ -154,11 +166,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 +248,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 })}
|
||||
|
||||
@@ -11,7 +11,12 @@ import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP, 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,
|
||||
@@ -329,6 +334,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)) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
`;
|
||||
|
||||
@@ -189,17 +189,6 @@ export const AccountMenu = () => {
|
||||
label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
|
||||
onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
|
||||
},
|
||||
{
|
||||
value: 'MobileDownload',
|
||||
icon:
|
||||
theme === AppTheme.Light ? (
|
||||
<Icon iconName={IconName.Sun} />
|
||||
) : (
|
||||
<Icon iconName={IconName.Moon} />
|
||||
),
|
||||
label: "Download Mobile App",
|
||||
onSelect: () => dispatch(openDialog({ type: DialogTypes.MobileDownload })),
|
||||
},
|
||||
...(onboardingState === OnboardingState.AccountConnected && hdKey
|
||||
? [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user