Compare commits

...

6 Commits

Author SHA1 Message Date
Aleka Cheung
1e1f2765d0
refresh on summary component load + fix rewards layout without migrate 2024-02-05 12:17:47 -05:00
Aleka Cheung
8e35cb872b
bump abacus version + fix profile layout 2024-02-05 12:17:46 -05:00
Aleka Cheung
babfd19602
address nits 2024-02-05 12:16:50 -05:00
Aleka Cheung
5172d1e117
add reward history tooltip 2024-02-05 12:16:50 -05:00
Aleka Cheung
92920eb7db
update grid layout on Rewards 2024-02-05 12:16:49 -05:00
Aleka Cheung
12d495c0b4
add historical trading rewards gated by feature flag 2024-02-05 12:16:45 -05:00
19 changed files with 835 additions and 224 deletions

View File

@ -39,7 +39,7 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.3.2",
"@dydxprotocol/v4-abacus": "^1.4.0",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.22",
"@ethersproject/providers": "^5.7.2",

179
pnpm-lock.yaml generated
View File

@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
follow-redirects: 1.15.3
@ -13,25 +9,25 @@ dependencies:
version: 1.10.0
'@cosmjs/amino':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@cosmjs/crypto':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@cosmjs/encoding':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@cosmjs/proto-signing':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@cosmjs/stargate':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@cosmjs/tendermint-rpc':
specifier: ^0.32.1
version: 0.32.1
version: 0.32.2
'@dydxprotocol/v4-abacus':
specifier: ^1.3.2
version: 1.3.2
specifier: ^1.4.0
version: 1.4.0
'@dydxprotocol/v4-client-js':
specifier: ^1.0.20
version: 1.0.20
@ -382,7 +378,7 @@ packages:
/@0xsquid/sdk@1.10.0:
resolution: {integrity: sha512-NKxHYB+g/TMPY+XmCHs+LuhyfbhH4KvAbGpVBOBPXM9Q5FsKcKrDJpTd5YnGYCLF9B3qXAzVTR0XhiC73GmOOA==}
dependencies:
'@cosmjs/encoding': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/stargate': 0.31.0
axios: 0.27.2
cosmjs-types: 0.8.0
@ -689,16 +685,16 @@ packages:
resolution: {integrity: sha512-xJ5CCEK7H79FTpOuEmlpSzVI+ZeYESTVvO3wHDgbnceIyAne3C68SvyaKqLUR4uJB0Z4q4+DZHbqW6itUiv4lA==}
dependencies:
'@cosmjs/crypto': 0.31.0
'@cosmjs/encoding': 0.31.1
'@cosmjs/math': 0.31.1
'@cosmjs/utils': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/math': 0.31.0
'@cosmjs/utils': 0.31.0
dev: false
/@cosmjs/amino@0.32.1:
resolution: {integrity: sha512-5l2xQ2XuAhV/B3kTIMPBcVZ/OQ+9Yyddzw/lIVs4qE5e/oBI0PVNWXw1oyR0wgfGHrMUxgKjsoOOqE2IbXVyCw==}
/@cosmjs/amino@0.32.2:
resolution: {integrity: sha512-lcK5RCVm4OfdAooxKcF2+NwaDVVpghOq6o/A40c2mHXDUzUoRZ33VAHjVJ9Me6vOFxshrw/XEFn1f4KObntjYA==}
dependencies:
'@cosmjs/crypto': 0.32.1
'@cosmjs/encoding': 0.32.1
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
dev: false
@ -753,19 +749,19 @@ packages:
/@cosmjs/crypto@0.31.0:
resolution: {integrity: sha512-UaqCe6Tgh0pe1QlZ66E13t6FlIF86QrnBXXq+EN7Xe1Rouza3fJ1ojGlPleJZkBoq3tAyYVIOOqdZIxtVj/sIQ==}
dependencies:
'@cosmjs/encoding': 0.31.1
'@cosmjs/math': 0.31.1
'@cosmjs/utils': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/math': 0.31.0
'@cosmjs/utils': 0.31.0
'@noble/hashes': 1.3.3
bn.js: 5.2.1
elliptic: 6.5.4
libsodium-wrappers-sumo: 0.7.11
dev: false
/@cosmjs/crypto@0.32.1:
resolution: {integrity: sha512-AsKucEg5o8evU0wXF/lDwX+ZSwCKF4bbc57nFzraHywlp3sNu4dfPPURoMrT0r7kT7wQZAy4Pdnvmm9nnCCm/Q==}
/@cosmjs/crypto@0.32.2:
resolution: {integrity: sha512-RuxrYKzhrPF9g6NmU7VEq++Hn1vZJjqqJpZ9Tmw9lOYOV8BUsv+j/0BE86kmWi7xVJ7EwxiuxYsKuM8IR18CIA==}
dependencies:
'@cosmjs/encoding': 0.32.1
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
'@noble/hashes': 1.3.3
@ -790,16 +786,16 @@ packages:
readonly-date: 1.0.0
dev: false
/@cosmjs/encoding@0.31.1:
resolution: {integrity: sha512-IuxP6ewwX6vg9sUJ8ocJD92pkerI4lyG8J5ynAM3NaX3q+n+uMoPRSQXNeL9bnlrv01FF1kIm8if/f5F7ZPtkA==}
/@cosmjs/encoding@0.31.0:
resolution: {integrity: sha512-NYGQDRxT7MIRSlcbAezwxK0FqnaSPKCH7O32cmfpHNWorFxhy9lwmBoCvoe59Kd0HmArI4h+NGzLEfX3OLnA4Q==}
dependencies:
base64-js: 1.5.1
bech32: 1.1.4
readonly-date: 1.0.0
dev: false
/@cosmjs/encoding@0.32.1:
resolution: {integrity: sha512-x60Lfds+Eq42rVV29NaoIAson3kBhATBI3zPp7X3GJTryBc5HFHQ6L/976tE1WB2DrvkfUdWS3ayCMVOY/qm1g==}
/@cosmjs/encoding@0.32.2:
resolution: {integrity: sha512-WX7m1wLpA9V/zH0zRcz4EmgZdAv1F44g4dbXOgNj1eXZw1PIGR12p58OEkLN51Ha3S4DKRtCv5CkhK1KHEvQtg==}
dependencies:
base64-js: 1.5.1
bech32: 1.1.4
@ -859,12 +855,6 @@ packages:
bn.js: 5.2.1
dev: false
/@cosmjs/math@0.31.1:
resolution: {integrity: sha512-kiuHV6m6DSB8/4UV1qpFhlc4ul8SgLXTGRlYkYiIIP4l0YNeJ+OpPYaOlEgx4Unk2mW3/O2FWYj7Jc93+BWXng==}
dependencies:
bn.js: 5.2.1
dev: false
/@cosmjs/math@0.32.2:
resolution: {integrity: sha512-b8+ruAAY8aKtVKWSft2IvtCVCUH1LigIlf9ALIiY8n9jtM4kMASiaRbQ/27etnSAInV88IaezKK9rQZrtxTjcw==}
dependencies:
@ -888,19 +878,19 @@ packages:
dependencies:
'@cosmjs/amino': 0.31.0
'@cosmjs/crypto': 0.31.0
'@cosmjs/encoding': 0.31.1
'@cosmjs/math': 0.31.1
'@cosmjs/utils': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/math': 0.31.0
'@cosmjs/utils': 0.31.0
cosmjs-types: 0.8.0
long: 4.0.0
dev: false
/@cosmjs/proto-signing@0.32.1:
resolution: {integrity: sha512-IHJMXQ8XnfzR5K1hWb8VV/jEfJof6BL2mgGIA7X4hSPegwoVfb9hnFKPEPgFjGCTTvGZ8SfnCdXxpsOjianVIA==}
/@cosmjs/proto-signing@0.32.2:
resolution: {integrity: sha512-UV4WwkE3W3G3s7wwU9rizNcUEz2g0W8jQZS5J6/3fiN0mRPwtPKQ6EinPN9ASqcAJ7/VQH4/9EPOw7d6XQGnqw==}
dependencies:
'@cosmjs/amino': 0.32.1
'@cosmjs/crypto': 0.32.1
'@cosmjs/encoding': 0.32.1
'@cosmjs/amino': 0.32.2
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
cosmjs-types: 0.9.0
@ -968,7 +958,7 @@ packages:
dependencies:
'@confio/ics23': 0.6.8
'@cosmjs/amino': 0.31.0
'@cosmjs/encoding': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/math': 0.31.0
'@cosmjs/proto-signing': 0.31.0
'@cosmjs/stream': 0.31.0
@ -984,16 +974,16 @@ packages:
- utf-8-validate
dev: false
/@cosmjs/stargate@0.32.1:
resolution: {integrity: sha512-S0E1qKQ2CMJU79G8bQTquTyrbU03gFsvCkbo3RvK8v2OltVCByjFNh+0nGN5do+uDOzwwmDvnNLhR+SaIyNQoQ==}
/@cosmjs/stargate@0.32.2:
resolution: {integrity: sha512-AsJa29fT7Jd4xt9Ai+HMqhyj7UQu7fyYKdXj/8+/9PD74xe6lZSYhQPcitUmMLJ1ckKPgXSk5Dd2LbsQT0IhZg==}
dependencies:
'@confio/ics23': 0.6.8
'@cosmjs/amino': 0.32.1
'@cosmjs/encoding': 0.32.1
'@cosmjs/amino': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/proto-signing': 0.32.1
'@cosmjs/proto-signing': 0.32.2
'@cosmjs/stream': 0.32.2
'@cosmjs/tendermint-rpc': 0.32.1
'@cosmjs/tendermint-rpc': 0.32.2
'@cosmjs/utils': 0.32.2
cosmjs-types: 0.9.0
xstream: 11.14.0
@ -1044,12 +1034,12 @@ packages:
resolution: {integrity: sha512-yo9xbeuI6UoEKIhFZ9g0dvUKLqnBzwdpEc/uldQygQc51j38gQVwFko+6sjmhieJqRYYvrYumcbJMiV6GFM9aA==}
dependencies:
'@cosmjs/crypto': 0.31.0
'@cosmjs/encoding': 0.31.1
'@cosmjs/encoding': 0.31.0
'@cosmjs/json-rpc': 0.31.0
'@cosmjs/math': 0.31.1
'@cosmjs/math': 0.31.0
'@cosmjs/socket': 0.31.0
'@cosmjs/stream': 0.31.0
'@cosmjs/utils': 0.31.1
'@cosmjs/utils': 0.31.0
axios: 0.21.4
readonly-date: 1.0.0
xstream: 11.14.0
@ -1059,11 +1049,11 @@ packages:
- utf-8-validate
dev: false
/@cosmjs/tendermint-rpc@0.32.1:
resolution: {integrity: sha512-4uGSxB2JejWhwBUgxca4GqcK/BGnCFMIP7ptwEledrC3AY/shPeIYcPXWEBwO7sfwCta8DhAOCLrc9zhVC+VAQ==}
/@cosmjs/tendermint-rpc@0.32.2:
resolution: {integrity: sha512-DXyJHDmcAfCix4H/7/dKR0UMdshP01KxJOXHdHxBCbLIpck94BsWD3B2ZTXwfA6sv98so9wOzhp7qGQa5malxg==}
dependencies:
'@cosmjs/crypto': 0.32.1
'@cosmjs/encoding': 0.32.1
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/json-rpc': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/socket': 0.32.2
@ -1090,10 +1080,6 @@ packages:
resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==}
dev: false
/@cosmjs/utils@0.31.1:
resolution: {integrity: sha512-n4Se1wu4GnKwztQHNFfJvUeWcpvx3o8cWhSbNs9JQShEuB3nv3R5lqFBtDCgHZF/emFQAP+ZjF8bTfCs9UBGhA==}
dev: false
/@cosmjs/utils@0.32.2:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
dev: false
@ -1102,29 +1088,29 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@1.3.2:
resolution: {integrity: sha512-zo0IHjGMlJRKOYDgNqNFQ9GtBJKJP4+Y9YY7V0X3Wt61ppKAYzodaYQhc9V/RYchcZTtS/xkicLug444YrvehQ==}
/@dydxprotocol/v4-abacus@1.4.0:
resolution: {integrity: sha512-mkdkXQXTVF1K6UmZwIwLC9gjsXg+Nt3h5/iW+WriVv1cUMPmHfPyRWL31eDDvkVO5UeECf05kiYXPk1y+7dPuA==}
dev: false
/@dydxprotocol/v4-client-js@1.0.20:
resolution: {integrity: sha512-dXKW2NC1XlVVIRKvHWVDofLZSCPTJAaRY5eXzxH5CcXpnl2kdXorr7ykqWZxW0jHFPWWvRSJtUDqZN1qFrEe/w==}
dependencies:
'@cosmjs/amino': 0.32.1
'@cosmjs/encoding': 0.32.1
'@cosmjs/amino': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/proto-signing': 0.32.1
'@cosmjs/stargate': 0.32.1
'@cosmjs/tendermint-rpc': 0.32.1
'@cosmjs/proto-signing': 0.32.2
'@cosmjs/stargate': 0.32.2
'@cosmjs/tendermint-rpc': 0.32.2
'@cosmjs/utils': 0.32.2
'@dydxprotocol/v4-proto': 4.0.0-dev.0
'@osmonauts/lcd': 0.6.0
'@scure/bip32': 1.3.3
'@scure/bip39': 1.2.2
'@scure/bip32': 1.3.2
'@scure/bip39': 1.2.1
axios: 1.1.3
bech32: 1.1.4
bignumber.js: 9.1.1
cosmjs-types: 0.9.0
ethereum-cryptography: 2.1.3
ethereum-cryptography: 2.1.2
ethers: 6.6.1
long: 4.0.0
protobufjs: 6.11.4
@ -2158,18 +2144,18 @@ packages:
'@noble/hashes': 1.3.0
dev: false
/@noble/curves@1.1.0:
resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==}
dependencies:
'@noble/hashes': 1.3.1
dev: false
/@noble/curves@1.2.0:
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
dependencies:
'@noble/hashes': 1.3.2
dev: false
/@noble/curves@1.3.0:
resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==}
dependencies:
'@noble/hashes': 1.3.3
dev: false
/@noble/ed25519@1.7.3:
resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==}
dev: true
@ -5035,18 +5021,18 @@ packages:
'@scure/base': 1.1.1
dev: false
/@scure/bip32@1.3.2:
resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==}
/@scure/bip32@1.3.1:
resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==}
dependencies:
'@noble/curves': 1.2.0
'@noble/curves': 1.1.0
'@noble/hashes': 1.3.3
'@scure/base': 1.1.5
dev: false
/@scure/bip32@1.3.3:
resolution: {integrity: sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==}
/@scure/bip32@1.3.2:
resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==}
dependencies:
'@noble/curves': 1.3.0
'@noble/curves': 1.2.0
'@noble/hashes': 1.3.3
'@scure/base': 1.1.5
dev: false
@ -5065,13 +5051,6 @@ packages:
'@scure/base': 1.1.5
dev: false
/@scure/bip39@1.2.2:
resolution: {integrity: sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==}
dependencies:
'@noble/hashes': 1.3.3
'@scure/base': 1.1.5
dev: false
/@solana/buffer-layout@4.0.1:
resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==}
engines: {node: '>=5.10'}
@ -5083,7 +5062,7 @@ packages:
resolution: {integrity: sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==}
dependencies:
'@babel/runtime': 7.22.10
'@noble/curves': 1.3.0
'@noble/curves': 1.2.0
'@noble/hashes': 1.3.3
'@solana/buffer-layout': 4.0.1
agentkeepalive: 4.5.0
@ -9307,13 +9286,13 @@ packages:
fast-safe-stringify: 2.1.1
dev: false
/ethereum-cryptography@2.1.3:
resolution: {integrity: sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==}
/ethereum-cryptography@2.1.2:
resolution: {integrity: sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==}
dependencies:
'@noble/curves': 1.3.0
'@noble/hashes': 1.3.3
'@scure/bip32': 1.3.3
'@scure/bip39': 1.2.2
'@noble/curves': 1.1.0
'@noble/hashes': 1.3.1
'@scure/bip32': 1.3.1
'@scure/bip39': 1.2.1
dev: false
/ethers@5.7.2:
@ -14933,3 +14912,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -65,6 +65,9 @@ type ElementProps = {
resolution?: number;
stripRelativeWords?: boolean;
};
timeOptions?: {
useUTC?: boolean;
};
tag?: React.ReactNode;
withParentheses?: boolean;
locale?: string;
@ -89,6 +92,7 @@ export const Output = ({
relativeTimeFormatOptions = {
format: 'singleCharacter',
},
timeOptions,
tag,
withParentheses,
locale = navigator.language || 'en-US',
@ -166,16 +170,21 @@ export const Output = ({
if ((typeof value !== 'string' && typeof value !== 'number') || !value) return null;
const date = new Date(value);
const dateString = {
[OutputType.Date]: date.toLocaleString(selectedLocale, { dateStyle: 'medium' }),
[OutputType.Date]: date.toLocaleString(selectedLocale, {
dateStyle: 'medium',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.DateTime]: date.toLocaleString(selectedLocale, {
dateStyle: 'short',
timeStyle: 'short',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.Time]: date.toLocaleString(selectedLocale, {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
}[type];

View File

@ -68,7 +68,7 @@ export type TableItem<TableRowData> = {
onSelect?: (key: TableRowData) => void;
};
type ColumnDef<TableRowData extends object> = {
export type ColumnDef<TableRowData extends object> = {
columnKey: string;
label: React.ReactNode;
tag?: React.ReactNode;
@ -513,7 +513,7 @@ const TableColumnHeader = <TableRowData extends object>({
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.Tr key="viewmore">
<Styled.ViewMoreTr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
@ -523,7 +523,7 @@ export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: ()
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.Tr>
</Styled.ViewMoreTr>
);
};
@ -673,6 +673,8 @@ Styled.TableWrapper = styled.div<{
--table-lastColumn-cell-align: end; // start | center | end | var(--table-cell-align)
--tableCell-padding: 0 1rem;
--tableViewMore-borderColor: inherit;
// Rules
flex: 1;
@ -984,3 +986,7 @@ Styled.ViewMoreButton = styled(Button)`
margin-left: 0.5ch;
}
`;
Styled.ViewMoreTr = styled(Styled.Tr)`
--border-color: var(--tableViewMore-borderColor);
`;

View File

@ -119,6 +119,13 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
// ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards;
export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward;
export const HistoricalTradingRewardsPeriod =
Abacus.exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod;
const historicalTradingRewardsPeriod = [...HistoricalTradingRewardsPeriod.values()] as const;
export type HistoricalTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number];
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
@ -202,6 +209,9 @@ export const RestrictionType = Abacus.exchange.dydx.abacus.output.Restriction;
const restrictionTypes = [...RestrictionType.values()] as const;
export type RestrictionTypes = (typeof restrictionTypes)[number];
// ------ Api data ------ //
export const ApiData = Abacus.exchange.dydx.abacus.state.manager.ApiData;
// ------ Enum Conversions ------ //
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? A
@ -236,6 +246,15 @@ export const HISTORICAL_PNL_PERIODS: Record<
[HistoricalPnlPeriod.Period90d.name]: HistoricalPnlPeriod.Period90d,
};
export const HISTORICAL_TRADING_REWARDS_PERIODS: Record<
KotlinIrEnumValues<typeof HistoricalTradingRewardsPeriod>,
HistoricalTradingRewardsPeriods
> = {
[HistoricalTradingRewardsPeriod.MONTHLY.name]: HistoricalTradingRewardsPeriod.MONTHLY,
[HistoricalTradingRewardsPeriod.WEEKLY.name]: HistoricalTradingRewardsPeriod.WEEKLY,
[HistoricalTradingRewardsPeriod.DAILY.name]: HistoricalTradingRewardsPeriod.DAILY,
};
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,

View File

@ -95,7 +95,7 @@ export type TooltipStrings = {
stringParams?: any;
urlConfigs?: LinksConfigs;
}) => {
title: string;
title?: string;
body: string;
learnMoreLink?: string;
};

View File

@ -198,4 +198,7 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_BODY }),
}),
'reward-history': ({ stringGetter }) => ({
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REWARD_HISTORY_BODY }),
}),
} as const;

View File

@ -3,6 +3,8 @@ import type { LocalWallet } from '@dydxprotocol/v4-client-js';
import type {
ClosePositionInputFields,
Nullable,
HistoricalTradingRewardsPeriod,
HistoricalTradingRewardsPeriods,
HumanReadablePlaceOrderPayload,
HumanReadableCancelOrderPayload,
TradeInputFields,
@ -23,6 +25,7 @@ import {
CoroutineTimer,
TransferType,
AbacusAppConfig,
ApiData,
} from '@/constants/abacus';
import { DEFAULT_MARKETID } from '@/constants/markets';
@ -33,8 +36,6 @@ import type { RootStore } from '@/state/_store';
import { setTradeFormInputs } from '@/state/inputs';
import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors';
import { testFlags } from '@/lib/testFlags';
import AbacusRest from './rest';
import AbacusAnalytics from './analytics';
import AbacusWebsocket from './websocket';
@ -227,6 +228,15 @@ class AbacusStateManager {
this.stateManager.historicalPnlPeriod = period;
};
setHistoricalTradingRewardPeriod = (
period: (typeof HistoricalTradingRewardsPeriod)[keyof typeof HistoricalTradingRewardsPeriod]
) => {
this.stateManager.historicalTradingRewardPeriod = period;
};
refreshHistoricalTradingRewards = () =>
this.stateManager.refresh(ApiData.HISTORICAL_TRADING_REWARDS);
switchNetwork = (network: DydxNetwork) => {
this.stateManager.environmentId = network;
@ -278,6 +288,9 @@ class AbacusStateManager {
getHistoricalPnlPeriod = (): Nullable<HistoricalPnlPeriods> =>
this.stateManager.historicalPnlPeriod;
getHistoricalTradingRewardPeriod = (): HistoricalTradingRewardsPeriods =>
this.stateManager.historicalTradingRewardPeriod;
handleCandlesSubscription = ({
channelId,
subscribe,

View File

@ -29,6 +29,7 @@ import {
setSubaccount,
setTransfers,
setWallet,
setTradingRewards,
} from '@/state/account';
import { setApiState } from '@/state/app';
@ -96,6 +97,12 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
}
}
if (changes.has(Changes.tradingRewards)) {
if (updatedState.account?.tradingRewards) {
dispatch(setTradingRewards(updatedState.account?.tradingRewards));
}
}
if (changes.has(Changes.configs)) {
dispatch(setConfigs(updatedState.configs));
}

View File

@ -26,6 +26,10 @@ class TestFlags {
get addressOverride():string {
return this.queryParams.address;
}
get showTradingRewards() {
return !!this.queryParams.tradingrewards;
}
}
export const testFlags = new TestFlags();

View File

@ -23,7 +23,10 @@ import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
import { wallets } from '@/constants/wallets';
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors';
import {
getHistoricalTradingRewardsForCurrentWeek,
getOnboardingState,
} from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { isTruthy } from '@/lib/isTruthy';
@ -52,6 +55,8 @@ const Profile = () => {
chainId: ENS_CHAIN_ID,
});
const currentWeekTradingReward = useSelector(getHistoricalTradingRewardsForCurrentWeek);
const actions = [
{
key: 'deposit',
@ -159,64 +164,62 @@ const Profile = () => {
);
})}
</Styled.Actions>
<Styled.EqualGrid>
<Styled.PanelButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.Gear} />
{stringGetter({ key: STRING_KEYS.SETTINGS })}
</Styled.InlineRow>
}
onClick={() => navigate(AppRoute.Settings)}
<Styled.SettingsButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.Gear} />
{stringGetter({ key: STRING_KEYS.SETTINGS })}
</Styled.InlineRow>
}
onClick={() => navigate(AppRoute.Settings)}
/>
<Styled.HelpButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.HelpCircle} />
{stringGetter({ key: STRING_KEYS.HELP })}
</Styled.InlineRow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
/>
<Styled.MigratePanel />
<Styled.DYDXBalancePanel />
<Styled.RewardsPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: currentWeekTradingReward?.amount ?? '-',
},
]}
layout="grid"
/>
<Styled.PanelButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.HelpCircle} />
{stringGetter({ key: STRING_KEYS.HELP })}
</Styled.InlineRow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
</Styled.RewardsPanel>
<Styled.FeesPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.MAKER }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.TAKER }), value: '-' },
{ key: 'volume', label: stringGetter({ key: STRING_KEYS.VOLUME_30D }), value: '-' },
]}
layout="grid"
/>
</Styled.EqualGrid>
</Styled.FeesPanel>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid>
<Styled.RewardsPanel
slotHeaderContent="Trading Rewards"
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: '-',
},
]}
layout="grid"
/>
</Styled.RewardsPanel>
<Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.MAKER }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.TAKER }), value: '-' },
{ key: 'volume', label: stringGetter({ key: STRING_KEYS.VOLUME_30D }), value: '-' },
]}
layout="grid"
/>
</Panel>
</Styled.EqualGrid>
<Styled.TablePanel
<Styled.HistoryPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.HISTORY })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Trades}`}
hasSeparator
@ -230,10 +233,10 @@ const Profile = () => {
]}
withInnerBorders={false}
/>
</Styled.TablePanel>
</Styled.HistoryPanel>
<GovernancePanel />
<StakingPanel />
<Styled.GovernancePanel />
<Styled.StakingPanel />
</Styled.MobileProfileLayout>
);
};
@ -245,14 +248,27 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.MobileProfileLayout = styled.div`
${layoutMixins.contentContainerPage}
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding: 1.25rem 0.9rem;
max-width: 100vw;
grid-template-areas:
'header header'
'actions actions'
'settings help'
'migrate migrate'
'balance balance'
'rewards fees'
'history history'
'governance governance'
'staking staking';
`;
Styled.Header = styled.header`
grid-area: header;
${layoutMixins.row}
padding: 0 1rem;
`;
@ -299,6 +315,7 @@ Styled.Address = styled.h1`
Styled.Actions = styled(Toolbar)`
${layoutMixins.spacedRow}
--stickyArea-topHeight: 5rem;
grid-area: actions;
> a,
> label {
@ -329,22 +346,16 @@ Styled.ActionButton = styled(IconButton)<{ iconName?: IconName }>`
`}
`;
Styled.EqualGrid = styled.div`
${layoutMixins.gridEqualColumns}
gap: 1rem;
`;
Styled.Details = styled(Details)`
font: var(--font-small-book);
--details-value-font: var(--font-medium-book);
`;
Styled.RewardsPanel = styled(Panel)`
grid-area: rewards;
align-self: flex-start;
&,
> * {
height: 100%;
> div {
height: 100%;
}
@ -353,7 +364,12 @@ Styled.RewardsPanel = styled(Panel)`
}
`;
Styled.TablePanel = styled(Panel)`
Styled.FeesPanel = styled(Panel)`
grid-area: fees;
`;
Styled.HistoryPanel = styled(Panel)`
grid-area: history;
--panel-content-paddingY: 0;
--panel-content-paddingX: 0;
@ -393,3 +409,27 @@ Styled.PanelButton = styled(Panel)`
--panel-paddingY: 0
--panel-paddingX:0;
`;
Styled.SettingsButton = styled(Styled.PanelButton)`
grid-area: settings;
`;
Styled.HelpButton = styled(Styled.PanelButton)`
grid-area: help;
`;
Styled.MigratePanel = styled(MigratePanel)`
grid-area: migrate;
`;
Styled.DYDXBalancePanel = styled(DYDXBalancePanel)`
grid-area: balance;
`;
Styled.GovernancePanel = styled(GovernancePanel)`
grid-area: governance;
`;
Styled.StakingPanel = styled(StakingPanel)`
grid-area: staking;
`;

View File

@ -22,7 +22,7 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
import { openDialog } from '@/state/dialogs';
import { calculateCanAccountTrade } from '@/state/accountCalculators';
export const DYDXBalancePanel = () => {
export const DYDXBalancePanel = ({ className }: { className?: string }) => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
@ -33,6 +33,7 @@ export const DYDXBalancePanel = () => {
return (
<Panel
className={className}
slotHeader={
<Styled.Header>
<Styled.Title>

View File

@ -0,0 +1,105 @@
import { useCallback, useState } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { useStringGetter } from '@/hooks';
import {
HISTORICAL_TRADING_REWARDS_PERIODS,
HistoricalTradingRewardsPeriod,
HistoricalTradingRewardsPeriods,
} from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { Panel } from '@/components/Panel';
import { ToggleGroup } from '@/components/ToggleGroup';
import { WithTooltip } from '@/components/WithTooltip';
import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTable';
import abacusStateManager from '@/lib/abacus';
export const RewardHistoryPanel = () => {
const stringGetter = useStringGetter();
const [selectedPeriod, setSelectedPeriod] = useState<HistoricalTradingRewardsPeriods>(
abacusStateManager.getHistoricalTradingRewardPeriod() || HistoricalTradingRewardsPeriod.WEEKLY
);
const onSelectPeriod = useCallback(
(periodName: string) => {
const selectedPeriod =
HISTORICAL_TRADING_REWARDS_PERIODS[
periodName as keyof typeof HISTORICAL_TRADING_REWARDS_PERIODS
];
setSelectedPeriod(selectedPeriod);
abacusStateManager.setHistoricalTradingRewardPeriod(selectedPeriod);
},
[setSelectedPeriod, selectedPeriod]
);
return (
<Panel
slotHeader={
<Styled.Header>
<Styled.Title>
<WithTooltip tooltip="reward-history">
<h3>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}</h3>
</WithTooltip>
<span>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION })}</span>
</Styled.Title>
<ToggleGroup
items={[
{
value: HistoricalTradingRewardsPeriod.MONTHLY.name,
label: stringGetter({ key: STRING_KEYS.MONTHLY }),
},
{
value: HistoricalTradingRewardsPeriod.WEEKLY.name,
label: stringGetter({ key: STRING_KEYS.WEEKLY }),
},
{
value: HistoricalTradingRewardsPeriod.DAILY.name,
label: stringGetter({ key: STRING_KEYS.DAILY }),
},
]}
value={selectedPeriod.name}
onValueChange={onSelectPeriod}
/>
</Styled.Header>
}
>
<TradingRewardHistoryTable period={selectedPeriod} />
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Header = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1rem 0;
margin-bottom: -0.5rem;
@media ${breakpoints.notTablet} {
padding: 1.25rem 1.5rem 0;
}
`;
Styled.Title = styled.div`
${layoutMixins.column}
color: var(--color-text-0);
font: var(--font-small-book);
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
`;
Styled.Content = styled.div`
${layoutMixins.flexColumn}
gap: 0.75rem;
`;

View File

@ -29,20 +29,18 @@ export const RewardsHelpPanel = () => {
>
<Accordion
items={[
{
header: 'Who is eligible for trading rewards?',
content: 'All traders are eligible for trading rewards.',
},
{
header: 'How do trading rewards work?',
content:
'Immediately after each fill, trading rewards are sent directly to the traders dYdX Chain address, based on the amount of fees paid by the trader.',
},
{
header: 'How do I claim my rewards?',
content:
'Each block, trading rewards are automatically sent directly to the traders dYdX Chain address.',
},
{
header: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_WHO_IS_ELIGIBLE_ANSWER }),
},
{
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER }),
},
{
header: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION }),
content: stringGetter({ key: STRING_KEYS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER }),
},
]}
/>
</Styled.HelpCard>
@ -72,7 +70,7 @@ Styled.Header = styled.div`
font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
padding: 1.5rem;
}
h3 {

View File

@ -1,5 +1,4 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
@ -11,18 +10,20 @@ import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel';
import { testFlags } from '@/lib/testFlags';
import { DYDXBalancePanel } from './DYDXBalancePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { MigratePanel } from './MigratePanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
import { TradingRewardsSummaryPanel } from './TradingRewardsSummaryPanel';
import { RewardHistoryPanel } from './RewardHistoryPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
import { NewMarketsPanel } from './NewMarketsPanel';
const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
@ -35,25 +36,38 @@ const RewardsPage = () => {
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
)}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
<Styled.GridLayout
showTradingRewards={testFlags.showTradingRewards}
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
>
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
{isTablet ? (
<LaunchIncentivesPanel />
) : (
<Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</Styled.PanelRowIncentivesAndBalance>
)}
{isNotTablet && (
<Styled.PanelRow>
<NewMarketsPanel />
<GovernancePanel />
<StakingPanel />
</Styled.PanelRow>
)}
{isTablet ? (
<Styled.LaunchIncentivesPanel />
) : (
<>
<Styled.LaunchIncentivesPanel />
<Styled.DYDXBalancePanel />
</>
)}
<RewardsHelpPanel />
{testFlags.showTradingRewards && (
<Styled.TradingRewardsColumn>
<TradingRewardsSummaryPanel />
{isTablet && <RewardsHelpPanel />}
<RewardHistoryPanel />
</Styled.TradingRewardsColumn>
)}
{isNotTablet && (
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
<NewMarketsPanel />
<GovernancePanel />
<StakingPanel />
<RewardsHelpPanel />
</Styled.OtherColumn>
)}
</Styled.GridLayout>
</Styled.Page>
);
};
@ -64,7 +78,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Page = styled.div`
${layoutMixins.contentContainerPage}
gap: 1.5rem;
padding: 2rem;
align-items: center;
@ -89,27 +102,98 @@ Styled.MobileHeader = styled.header`
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
margin-bottom: -1.5rem;
font: var(--font-large-medium);
color: var(--color-text-2);
background-color: var(--color-layer-2);
`;
Styled.Panel = styled(Panel)`
height: fit-content;
`;
Styled.GridLayout = styled.div<{ showTradingRewards?: boolean; showMigratePanel?: boolean }>`
--gap: 1.5rem;
display: grid;
grid-template-columns: 2fr 1fr;
gap: var(--gap);
Styled.PanelRow = styled.div`
${layoutMixins.gridEqualColumns}
gap: 1.5rem;
> * {
gap: var(--gap);
}
${({ showTradingRewards, showMigratePanel }) =>
showTradingRewards && showMigratePanel
? css`
grid-template-areas:
'migrate migrate'
'incentives balance'
'rewards other';
`
: showTradingRewards
? css`
grid-template-areas: 'incentives balance' 'rewards other';
`
: showMigratePanel
? css`
grid-template-areas: 'migrate migrate' 'incentives balance' 'other other';
`
: css`
grid-template-areas: 'incentives balance' 'other other';
`};
@media ${breakpoints.tablet} {
grid-auto-flow: row;
--gap: 1rem;
grid-template-columns: 1fr;
${({ showTradingRewards }) =>
showTradingRewards
? css`
grid-template-areas:
'incentives'
'rewards';
`
: css`
grid-template-areas: 'incentives';
`}
}
`;
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
grid-template-columns: 2fr 1fr;
Styled.MigratePanel = styled(MigratePanel)`
grid-area: migrate;
`;
Styled.LaunchIncentivesPanel = styled(LaunchIncentivesPanel)`
grid-area: incentives;
`;
Styled.DYDXBalancePanel = styled(DYDXBalancePanel)`
grid-area: balance;
`;
Styled.TradingRewardsColumn = styled.div`
grid-area: rewards;
${layoutMixins.flexColumn}
`;
Styled.OtherColumn = styled.div<{ showTradingRewards?: boolean }>`
grid-area: other;
${layoutMixins.flexColumn}
${({ showTradingRewards }) =>
!showTradingRewards &&
css`
display: grid;
grid-template-columns: repeat(3, 1fr);
> section:last-of-type {
grid-column: 1 / -1;
}
`}
`;
Styled.RewardHistoryHeader = styled.div`
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
}
padding: 1rem 1.5rem 0;
margin-bottom: -0.5rem;
`;

View File

@ -0,0 +1,134 @@
import { useEffect } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output';
import { Panel } from '@/components/Panel';
import { getHistoricalTradingRewardsForCurrentWeek } from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus';
export const TradingRewardsSummaryPanel = () => {
const stringGetter = useStringGetter();
const { chainTokenLabel } = useTokenConfigs();
const currentWeekTradingReward = useSelector(getHistoricalTradingRewardsForCurrentWeek, shallowEqual);
useEffect(() => {
abacusStateManager.refreshHistoricalTradingRewards();
}, []);
return !currentWeekTradingReward ? null : (
<Panel
slotHeader={
<Styled.Header>{stringGetter({ key: STRING_KEYS.TRADING_REWARDS_SUMMARY })}</Styled.Header>
}
>
<Styled.Content>
<Styled.TradingRewardsDetails
layout="grid"
items={[
{
key: 'week',
label: (
<Styled.Label>
<h4>{stringGetter({ key: STRING_KEYS.THIS_WEEK })}</h4>
</Styled.Label>
),
value: (
<Styled.Column>
<Output
slotRight={<Styled.AssetIcon symbol={chainTokenLabel} />}
type={OutputType.Asset}
value={currentWeekTradingReward.amount}
/>
<Styled.TimePeriod>
<Output
type={OutputType.Date}
value={currentWeekTradingReward.startedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
<Output
type={OutputType.Date}
value={currentWeekTradingReward.endedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
</Styled.TimePeriod>
</Styled.Column>
),
},
// TODO(@aforaleka): add all-time when supported
]}
/>
</Styled.Content>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Header = styled.div`
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
font: var(--font-medium-book);
color: var(--color-text-2);
`;
Styled.Content = styled.div`
${layoutMixins.flexColumn}
gap: 0.75rem;
`;
Styled.TradingRewardsDetails = styled(Details)`
--details-item-backgroundColor: var(--color-layer-6);
grid-template-columns: 1fr; // TODO(@aforaleka): change to 1fr 1fr when all-time is supported
gap: 1rem;
> div {
gap: 0.5rem;
padding: 1rem;
border-radius: 0.75em;
background-color: var(--color-layer-5);
}
dt {
width: 100%;
}
output {
color: var(--color-text-2);
font: var(--font-large-book);
}
`;
Styled.Label = styled.div`
${layoutMixins.spacedRow}
font: var(--font-base-book);
color: var(--color-text-1);
`;
Styled.TimePeriod = styled.div`
${layoutMixins.inlineRow}
&, output {
color: var(--color-text-0);
font: var(--font-small-book);
}
`;
Styled.Column = styled.div`
${layoutMixins.flexColumn}
gap: 0.33rem;
`;
Styled.AssetIcon = styled(AssetIcon)`
margin-left: 0.5ch;
`;

View File

@ -13,6 +13,7 @@ import type {
HistoricalPnlPeriods,
SubAccountHistoricalPNLs,
UsageRestriction,
TradingRewards,
} from '@/constants/abacus';
import { OnboardingGuard, OnboardingState } from '@/constants/account';
@ -24,6 +25,7 @@ import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = {
balances?: Record<string, AccountBalance>;
stakingBalances?: Record<string, AccountBalance>;
tradingRewards?: TradingRewards;
wallet?: Nullable<Wallet>;
walletType?: WalletType;
@ -179,6 +181,9 @@ export const accountSlice = createSlice({
setStakingBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
state.stakingBalances = action.payload;
},
setTradingRewards: (state, action: PayloadAction<TradingRewards>) => {
state.tradingRewards = action.payload;
},
addUncommittedOrderClientId: (state, action: PayloadAction<number>) => {
state.uncommittedOrderClientIds.push(action.payload);
},
@ -206,6 +211,7 @@ export const {
viewedOrders,
setBalances,
setStakingBalances,
setTradingRewards,
addUncommittedOrderClientId,
removeUncommittedOrderClientId,
} = accountSlice.actions;

View File

@ -1,3 +1,4 @@
import type { Nullable, kollections } from '@dydxprotocol/v4-abacus';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { createSelector } from 'reselect';
@ -9,6 +10,8 @@ import {
AbacusOrderStatus,
AbacusPositionSide,
ORDER_SIDES,
HistoricalTradingReward,
HistoricalTradingRewardsPeriod,
} from '@/constants/abacus';
import { OnboardingState } from '@/constants/account';
@ -349,6 +352,38 @@ export const getBalances = (state: RootState) => state.account?.balances;
* */
export const getStakingBalances = (state: RootState) => state.account?.stakingBalances;
/**
* @returns account all time trading rewards
*/
export const getTotalTradingRewards = (state: RootState) => state.account?.tradingRewards?.total;
/**
* @returns account trading rewards aggregated by period
*/
export const getHistoricalTradingRewards = (state: RootState) =>
state.account?.tradingRewards?.historical;
/**
* @returns account historical trading rewards for the specified perid
*/
export const getHistoricalTradingRewardsForPeriod = (period: string) =>
createSelector(
[getHistoricalTradingRewards],
(
historicalTradingRewards: Nullable<
kollections.Map<string, kollections.List<HistoricalTradingReward>>
>
) => historicalTradingRewards?.get(period)
);
/**
* @returns account historical trading rewards for the current week
*/
export const getHistoricalTradingRewardsForCurrentWeek = createSelector(
[getHistoricalTradingRewardsForPeriod(HistoricalTradingRewardsPeriod.WEEKLY.name)],
(historicalTradingRewards) => historicalTradingRewards?.firstOrNull()
);
/**
* @returns UsageRestriction of the current session
*/

View File

@ -0,0 +1,164 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { HistoricalTradingRewardsPeriods, HistoricalTradingReward } from '@/constants/abacus';
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { AssetIcon } from '@/components/AssetIcon';
import { Output, OutputType } from '@/components/Output';
import { Table, TableCell, type ColumnDef } from '@/components/Table';
import {
getHistoricalTradingRewards,
getHistoricalTradingRewardsForPeriod,
} from '@/state/accountSelectors';
export enum TradingRewardHistoryTableColumnKey {
Event = 'Event',
Earned = 'Earned',
}
const getTradingRewardHistoryTableColumnDef = ({
key,
chainTokenLabel,
stringGetter,
}: {
key: TradingRewardHistoryTableColumnKey;
chainTokenLabel: string;
stringGetter: StringGetterFunction;
}): ColumnDef<HistoricalTradingReward> => ({
...(
{
[TradingRewardHistoryTableColumnKey.Event]: {
columnKey: TradingRewardHistoryTableColumnKey.Event,
getCellValue: (row) => row.startedAtInMilliseconds,
label: stringGetter({ key: STRING_KEYS.EVENT }),
renderCell: ({ startedAtInMilliseconds, endedAtInMilliseconds }) => (
<TableCell stacked>
<Styled.Rewarded>{stringGetter({ key: STRING_KEYS.REWARDED })}</Styled.Rewarded>
<Styled.TimePeriod>
{stringGetter({
key: STRING_KEYS.FOR_TRADING,
params: {
PERIOD: (
<>
<Output
type={OutputType.Date}
value={startedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
<Output
type={OutputType.Date}
value={endedAtInMilliseconds}
timeOptions={{ useUTC: true }}
/>
</>
),
},
})}
</Styled.TimePeriod>
</TableCell>
),
},
[TradingRewardHistoryTableColumnKey.Earned]: {
columnKey: TradingRewardHistoryTableColumnKey.Earned,
getCellValue: (row) => row.amount,
label: stringGetter({ key: STRING_KEYS.EARNED }),
renderCell: ({ amount }) => (
<Output
type={OutputType.Asset}
value={amount}
slotRight={<Styled.AssetIcon symbol={chainTokenLabel} />}
/>
),
},
} as Record<TradingRewardHistoryTableColumnKey, ColumnDef<HistoricalTradingReward>>
)[key],
});
type ElementProps = {
columnKeys?: TradingRewardHistoryTableColumnKey[];
period: HistoricalTradingRewardsPeriods;
};
type StyleProps = {
withOuterBorder?: boolean;
withInnerBorders?: boolean;
};
export const TradingRewardHistoryTable = ({
period,
columnKeys = Object.values(TradingRewardHistoryTableColumnKey),
withOuterBorder,
withInnerBorders = true,
}: ElementProps & StyleProps) => {
const stringGetter = useStringGetter();
const { chainTokenLabel } = useTokenConfigs();
const periodTradingRewards = useSelector(
getHistoricalTradingRewardsForPeriod(period.name),
shallowEqual
);
return (
<Styled.Table
label={stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}
data={periodTradingRewards?.toArray() ?? []}
getRowKey={(row: any) => row.startedAtInMilliseconds}
columns={columnKeys.map((key: TradingRewardHistoryTableColumnKey) =>
getTradingRewardHistoryTableColumnDef({
key,
chainTokenLabel,
stringGetter,
})
)}
slotEmpty={
<div>{stringGetter({ key: STRING_KEYS.EMPTY_HISTORICAL_REWARDS_DESCRIPTION })}</div>
}
selectionBehavior="replace"
withOuterBorder={withOuterBorder}
withInnerBorders={withInnerBorders}
initialNumRowsToShow={5}
withScrollSnapColumns
withScrollSnapRows
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Table = styled(Table)`
--tableCell-padding: 0.5rem 0;
--tableHeader-backgroundColor: var(--color-layer-3);
--tableRow-backgroundColor: var(--color-layer-3);
--tableViewMore-borderColor: var(--color-layer-3);
tbody {
font: var(--font-medium-book);
}
`;
Styled.Rewarded = styled.span`
color: var(--color-text-2);
`;
Styled.TimePeriod = styled.div`
${layoutMixins.inlineRow}
&& {
color: var(--color-text-0);
font: var(--font-base-book);
}
output {
color: var(--color-text-1);
font: var(--font-base-book);
}
`;
Styled.AssetIcon = styled(AssetIcon)`
margin-left: 0.5ch;
`;