Compare commits

...

29 Commits

Author SHA1 Message Date
Linkie Link
4ebcc44ded
fix: filter available networks 2024-02-23 15:14:42 +01:00
Linkie Link
b4647ff440
feat: created a new ChainSelect 2024-02-23 15:10:03 +01:00
Linkie Link
103246261f
fix: introduce shortened connect button 2024-02-23 11:46:12 +01:00
Linkie Link
860079b071
Merge branch 'lend-table-adjustments' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-23 11:40:42 +01:00
Linkie Link
51e02eec8c
feat: first v1 standalone adjustments 2024-02-23 11:40:28 +01:00
Linkie Link
071cceacec
feat: separate v1 implementation 2024-02-21 08:03:17 +01:00
Linkie Link
89e9942b7e
tidy: remove sentry 2024-02-21 07:47:08 +01:00
Linkie Link
1a45d64bac
fix: fix getUrl 2024-02-19 13:10:24 +01:00
Linkie Link
89d41fa5cf
fix: isNaN Leverage 2024-02-19 12:38:04 +01:00
Linkie Link
df7ef30a43
Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-19 09:28:25 +01:00
Linkie Link
fc9fcf13fc
fix: fixed lending table widths 2024-02-16 17:15:59 +01:00
Linkie Link
5846e893d1
Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into develop 2024-02-16 09:07:22 +01:00
Linkie Link
21bedd7905
feat: added borrow and repay 2024-02-15 21:51:00 +01:00
Linkie Link
37be3240eb
feat: finished deposit and withdraw 2024-02-15 20:40:22 +01:00
Linkie Link
f609540809
feat: prepared everything for modal interaction 2024-02-15 16:22:20 +01:00
Linkie Link
106de661a8
feat: added v1 based buttons and a portfolio summary 2024-02-15 16:08:04 +01:00
Linkie Link
40593ae987
Merge branch 'develop' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation
# Conflicts:
#	src/components/borrow/Table/Columns/useDepositedColumns.tsx
#	src/components/borrow/Table/DepositedBorrowingsTable.tsx
#	src/components/earn/lend/Table/DepositedLendsTable.tsx
2024-02-15 14:11:12 +01:00
Linkie Link
e3b5e330ee
Merge branch 'main' of https://github.com/mars-protocol/mars-v2-frontend into develop
# Conflicts:
#	.env
#	src/components/perps/BalancesTable/index.tsx
2024-02-14 19:30:55 +01:00
Linkie Link
7eac8e7a35
Update Dockerbuild (#813)
* v2.2.4 (#810)

* feat: handle URLs with or without trailing slash (#803)

* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes

* Fix docker build (#805)

* fix: fixed the docker build

* tidy: cleanup

* env: remove env contents (#808)

* Portfolio fix (#809)

* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor

* align buttons, perps row clickable (#807)

* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>

* Update Dockerfile

* remove   .env files

* tidy: format

---------

Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
2024-02-14 19:29:41 +01:00
Linkie Link
af478c56d5
Update .env.production 2024-02-14 16:43:57 +01:00
Bob van der Helm
a6f4c24f15
align buttons, perps row clickable (#807)
* align buttons, perps row clickable

* fix comments

* update to v2.2.4

* fix borrowbutton logic, add vault deposit manage btn, fix icon size vault modal

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
2024-02-14 14:49:15 +01:00
Linkie Link
6946ceddfc
Portfolio fix (#809)
* fix: fixed the portfolio account detail page layout

* fix: fixed portfolio cards

* tidy: refactor
2024-02-14 14:18:10 +01:00
Linkie Link
0154065ffb
env: remove env contents (#808) 2024-02-14 12:33:10 +01:00
Linkie Link
4f64234a75
Merge branch 'main' into develop 2024-02-14 12:01:10 +01:00
Linkie Link
26f1ef4a2c
Fix docker build (#805)
* fix: fixed the docker build

* tidy: cleanup
2024-02-14 11:58:48 +01:00
Linkie Link
34db8aea1a
tidy: streamline borrow 2024-02-14 11:14:40 +01:00
Linkie Link
75edb21c02
Merge branch 'develop' of https://github.com/mars-protocol/mars-v2-frontend into v1-implementation 2024-02-14 11:11:42 +01:00
Linkie Link
442b7a3a8c
feat: implement v1 tables into v2 with data fetching 2024-02-14 11:11:34 +01:00
Linkie Link
3f28ccd09c
feat: handle URLs with or without trailing slash (#803)
* feat: handle URLs with or without trailing slash

* tidy: cleanup slashes
2024-02-14 09:26:10 +01:00
108 changed files with 1980 additions and 337 deletions

View File

@ -1,6 +1,7 @@
{
"name": "mars-v2-frontend",
"version": "2.2.4",
"v1version": "1.7.5",
"homepage": "./",
"private": false,
"license": "SEE LICENSE IN LICENSE FILE",

63
public/images/bg-v1.svg Normal file
View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
opacity="0.05"
viewBox="0 0 1440 819.8"
>
<path
stroke="#FFF"
d="M-41.5,81c20,17.3,62.5,51.4,71.8,49.5s27.7,13.3,35.6,21.1
c19.3,16.7,58.9,49.8,62.9,48.9c1.2-0.3,5.1-0.3,10.8-0.2c17.4,0.1,51.4,0.4,76.6-8.2c33.4-11.5,50.7-6.6,61.8,0s71.8,37.4,82.4,35
c3.5-0.8,14-0.1,26.7,0.7c25.2,1.7,59.4,3.9,66.8-3.7c11.1-11.5,204.9-19.3,223.3-9.7c14.7,7.7,34.1,7.8,42.7,7.8s28-0.1,42.7-7.8
c18.4-9.7,212.2-1.8,223.3,9.7c7.4,7.6,41.6,5.4,66.8,3.7c12.7-0.8,23.2-1.5,26.7-0.7c10.6,2.4,71.3-28.4,82.4-35
c11.1-6.6,28.4-11.5,61.8,0c25.2,8.6,59.3,8.4,76.6,8.2c5.7,0,9.6-0.1,10.8,0.2c4,1,43.6-32.2,62.9-48.9c8-7.8,26.3-23.1,35.6-21.1
c9.4,1.9,51.8-32.2,71.8-49.5"
/>
<path
stroke="#FFF"
d="M-39.8,340.5c8.1,0.1,15.5,0.1,21.7,0.1
c18.9,0,60.1,10.9,95.2,30.4c2.9,0.7,5.7,1.4,8.3,2c17.6,4.3,28.9,7.1,50.7,7.1l152,3c2.6-0.4,8.9-2.6,13.4-7.9
c4.5-5.3,15.6-14.4,20.6-18.2c3.5-2.2,13.6-6.7,25.6-6.7c9.5,0,21.5,0.7,30.3,1.3c5.1,0.3,9.1,0.6,10.9,0.6c5,0,17.3-3,21.7-7.9
c3.6-3.9,39.4-47,56.8-68c6.1-6.9,22.4-19.3,38.4-14c20,6.7,75.2,6.7,84.1,0c8.9-6.7,29.5-18.2,49.6-14.6c2.2,0.4,4.8,0.9,7.8,1.4
c24.2,4.4,70.3,12.8,72.8,12.8s48.6-8.4,72.8-12.8c3-0.5,5.6-1,7.8-1.4c20-3.6,40.6,7.9,49.6,14.6c8.9,6.7,64,6.7,84.1,0
c16-5.3,32.3,7.1,38.4,14c17.4,21.1,53.2,64.1,56.8,68c4.5,4.9,16.7,7.9,21.7,7.9c1.8,0,5.8-0.2,10.9-0.6c8.8-0.5,20.8-1.3,30.3-1.3
c12,0,22.1,4.5,25.6,6.7c5,3.8,16.1,12.9,20.6,18.2c4.4,5.3,10.8,7.5,13.4,7.9l152-3c21.8,0,33.1-2.8,50.7-7.1
c2.6-0.6,5.4-1.3,8.3-2c35.1-19.4,76.3-30.4,95.2-30.4c6.2,0,13.6-0.1,21.7-0.1"
/>
<path
stroke="#FFF"
d="M-56.4,455c79.7-9.9,188.1-23.3,214.2-34.7
c17-2.8,93.6,6,161.8,13.8c52.2,6,99.5,11.3,111.1,10.5c8.2-0.6,28.2-8.6,55.3-19.5C546.6,400.7,642.6,362,720,362
s173.4,38.7,234.1,63.1c27,10.9,47,19,55.3,19.5c11.6,0.8,58.8-4.6,111.1-10.5c68.1-7.8,144.8-16.5,161.8-13.8
c26.1,11.4,134.5,24.8,214.2,34.7"
/>
<path
stroke="#FFF"
d="M-22.1,455.3c12.3,3,52.3,21.3,64,37.1
c11.7,15.8,33.3,14.7,35.1,12.8c1.7-1.8,16.7-13,24.5-18.2c4.6-2.4,16.8-5.8,28.4,0s79.4,39.7,111.9,55.9c4.3,2.4,13.1,7.3,14.5,7.3
c0.2,0,0.8,0.1,1.6,0.1c4.7,0.4,17.3,1.6,20.7-2.6c3.1-3.9,20.6-17.4,29-23.7c6.1-4,20.9-12.1,31.2-12.1s82.6,4,117.5,6.1
c20.2-0.4,61.8-1.3,66.3-1.8c3.3-0.4,34.5,1.6,60.7,3.3c17.8,1.1,33.4,2.1,36.2,2.1c3.4,0,13.7-7.1,26.7-16.3
c23.1-16.1,55-38.4,73.8-38.4s50.8,22.3,73.8,38.4c13.1,9.1,23.3,16.3,26.7,16.3c2.8,0,18.4-1,36.2-2.1c26.2-1.7,57.4-3.7,60.7-3.3
c4.5,0.5,46,1.4,66.3,1.8c34.9-2,107.2-6.1,117.5-6.1c10.2,0,25.1,8.1,31.2,12.1c8.3,6.3,25.8,19.8,29,23.7c3.3,4.1,16,3,20.7,2.6
c0.8-0.1,1.4-0.1,1.6-0.1c1.3,0,10.2-4.9,14.5-7.3c32.5-16.2,100.3-50.1,111.9-55.9c11.6-5.8,23.8-2.4,28.4,0
c7.8,5.3,22.8,16.4,24.5,18.2c1.8,1.9,23.4,3,35.1-12.8c11.7-15.8,51.8-34,64-37.1"
/>
<path
stroke="#FFF"
d="M-48.7,749.9c4.2,0.6,7.8,1.2,10.5,1.2c3.3,0,5.6-2.4,8-5
c2.8-3,5.8-6.3,10.9-5.9c7.6,0.5,75.5,0.2,108.6,0l11.7,2.4l53.5,35.8l6.1,10.9l34.5,3l35.6,12.8c13,3.4,40.2,10.1,45.1,9.1
c3.1-0.6,12.4-7.5,21.6-14.4c9-6.7,17.9-13.3,21.2-14.2c6.7-1.8,27.3-11.5,30.6-14.6c0.3-0.3,0.7-0.7,1.3-1.2
c5.5-5.3,22.7-21.6,33.8-18.8c5.1,1.3,10,4.1,14.9,6.8c6.9,3.9,13.4,7.6,19.6,6.5c7.1-1.2,29.3-20.4,44.1-33.1
c7.3-6.3,12.8-11,13.8-11.2c2.2-0.5,38.4-39.1,56.2-58.3c5.9-6.3,23.6-14.8,42.3-6.1c18.7,8.7,46,9.1,57.9,9.1l67.9,6.1l18.7-0.1
l18.7,0.1l67.9-6.1c11.9,0,39.2-0.4,57.9-9.1c18.7-8.7,36.4-0.2,42.3,6.1c17.8,19.2,54,57.8,56.2,58.3c0.9,0.2,6.4,4.9,13.8,11.2
c14.8,12.7,37.1,31.9,44.1,33.1c6.2,1.1,12.8-2.7,19.6-6.5c4.8-2.7,9.8-5.6,14.9-6.8c11.1-2.8,28.2,13.6,33.8,18.8
c0.6,0.5,1,0.9,1.3,1.2c3.3,3,23.9,12.8,30.6,14.6c3.3,0.9,12.3,7.5,21.2,14.2c9.2,6.9,18.5,13.7,21.6,14.4c4.9,1,32.1-5.7,45.1-9.1
l35.6-12.8l34.5-3l6.1-10.9l53.5-35.8l11.7-2.4c33,0.2,101,0.5,108.6,0c5.1-0.3,8.1,2.9,10.9,5.9c2.4,2.6,4.7,5,8,5
c2.7,0,6.3-0.5,10.5-1.2"
/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,42 @@
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M100.881 178.044C144.869 178.044 180.531 142.388 180.531 98.4C180.531 54.4124 144.869 18.75 100.881 18.75C56.8939 18.75 21.2378 54.4124 21.2378 98.4C21.2378 142.388 56.8939 178.044 100.881 178.044Z"
fill="#2E3148"
/>
<path
d="M100.694 143.475C126.106 143.475 146.712 122.869 146.712 97.4562C146.712 72.0437 126.106 51.4375 100.694 51.4375C75.2749 51.4375 54.6748 72.0437 54.6748 97.4562C54.6748 122.869 75.2749 143.475 100.694 143.475Z"
fill="#1B1E36"
/>
<path
d="M200 97.5C200 110.631 197.412 123.631 192.387 135.769C187.362 147.9 179.994 158.925 170.712 168.206C161.425 177.494 150.4 184.862 138.269 189.887C126.137 194.912 113.131 197.5 100 197.5C86.8687 197.5 73.8625 194.912 61.7313 189.887C49.6001 184.862 38.5748 177.494 29.2873 168.206C20.0061 158.925 12.6376 147.9 7.61262 135.769C2.58761 123.631 0 110.631 0 97.5H23.9689C23.9689 107.481 25.9312 117.369 29.7562 126.594C33.5749 135.819 39.1751 144.2 46.2376 151.262C53.3001 158.319 61.6811 163.925 70.9063 167.744C80.1313 171.563 90.0125 173.531 100 173.531C109.988 173.531 119.869 171.563 129.094 167.744C138.319 163.925 146.7 158.319 153.762 151.262C160.825 144.2 166.425 135.819 170.244 126.594C174.069 117.369 176.031 107.481 176.031 97.5H200Z"
fill="#C73238"
/>
<path
d="M178.95 97.5C178.95 107.869 176.906 118.131 172.937 127.712C168.969 137.287 163.156 145.994 155.825 153.325C148.494 160.656 139.794 166.469 130.212 170.438C120.637 174.406 110.369 176.444 99.9998 176.444C89.631 176.444 79.3685 174.406 69.7873 170.438C60.2124 166.469 51.506 160.656 44.1747 153.325C36.8435 145.994 31.0312 137.287 27.0624 127.712C23.0937 118.131 21.0562 107.869 21.0562 97.5H99.9998H178.95Z"
fill="#070B09"
/>
<path
d="M99.2748 28.6934C90.8686 28.6934 84.0498 59.1809 84.0498 96.7934C84.0498 134.406 90.8686 164.893 99.2748 164.893C107.681 164.893 114.5 134.406 114.5 96.7934C114.5 59.1809 107.681 28.6934 99.2748 28.6934ZM100.325 161.05C99.3623 162.331 98.3998 161.368 98.3998 161.368C94.5311 156.887 92.5936 148.55 92.5936 148.55C85.8248 126.762 87.4373 79.9684 87.4373 79.9684C90.6186 42.8247 96.4061 34.0495 98.3748 32.0995C98.5811 31.8995 98.8436 31.7808 99.1248 31.7558C99.4061 31.737 99.6873 31.8121 99.9186 31.9746C102.775 33.9996 105.175 42.4745 105.175 42.4745C112.256 68.7496 111.612 93.4309 111.612 93.4309C112.256 114.9 108.069 138.937 108.069 138.937C104.844 157.2 100.325 161.05 100.325 161.05Z"
fill="white"
/>
<path
d="M158.338 62.9C154.156 55.6063 124.319 64.8687 91.6814 83.5812C59.0376 102.3 36.0126 123.387 40.2001 130.675C44.3814 137.969 74.2189 128.713 106.856 109.994C139.5 91.275 162.525 70.1875 158.338 62.9ZM44.0564 129.675C42.4564 129.475 42.8189 128.156 42.8189 128.156C44.7814 122.569 51.0377 116.744 51.0377 116.744C66.5814 100.025 107.969 78.1375 107.969 78.1375C141.769 62.4126 152.269 63.075 154.931 63.8062C155.206 63.8875 155.444 64.0562 155.6 64.2875C155.763 64.525 155.831 64.8062 155.806 65.0875C155.481 68.5812 149.313 74.8687 149.313 74.8687C130.056 94.1 108.331 105.825 108.331 105.825C90.0252 117.069 67.0939 125.394 67.0939 125.394C49.6437 131.681 44.0564 129.675 44.0564 129.675Z"
fill="white"
/>
<path
d="M158.194 130.919C162.412 123.644 139.45 102.457 106.925 83.6007C74.3998 64.7445 44.5623 55.3695 40.3498 62.657C36.1373 69.9445 59.1 91.1132 91.6436 109.969C124.187 128.832 153.981 138.207 158.194 130.919ZM43.1437 65.4695C42.5249 63.9882 43.8373 63.6382 43.8373 63.6382C49.6623 62.5382 57.8376 65.0382 57.8376 65.0382C80.0873 70.107 119.769 94.9632 119.769 94.9632C150.312 116.338 155 125.751 155.7 128.426C155.775 128.701 155.744 128.988 155.625 129.244C155.5 129.501 155.294 129.707 155.037 129.819C151.85 131.282 143.319 129.107 143.319 129.107C117.025 122.057 95.9936 109.138 95.9936 109.138C77.0998 98.9507 58.4124 83.2695 58.4124 83.2695C44.2187 71.3132 43.1498 65.482 43.1498 65.482L43.1437 65.4695Z"
fill="white"
/>
<path
d="M99.1126 104.63C103.538 104.63 107.125 101.043 107.125 96.618C107.125 92.193 103.538 88.6055 99.1126 88.6055C94.6876 88.6055 91.1001 92.193 91.1001 96.618C91.1001 101.043 94.6876 104.63 99.1126 104.63Z"
fill="white"
/>
<path
d="M131.969 70.6691C134.531 70.6691 136.613 68.5191 136.613 65.8628C136.613 63.2066 134.531 61.0566 131.969 61.0566C129.4 61.0566 127.319 63.2066 127.319 65.8628C127.319 68.5191 129.4 70.6691 131.969 70.6691Z"
fill="white"
/>
<path
d="M55.6873 87.0133C58.2498 87.0133 60.3313 84.857 60.3313 82.207C60.3313 79.5508 58.2498 77.3945 55.6873 77.3945C53.1185 77.3945 51.0376 79.5508 51.0376 82.207C51.0376 84.857 53.1185 87.0133 55.6873 87.0133Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -0,0 +1,26 @@
<svg viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="256" fill="#00A3FF" />
<path
opacity="0.6"
d="M361.012 237.812L363.878 242.209C396.204 291.798 388.984 356.742 346.52 398.348C321.539 422.826 288.798 435.066 256.056 435.069C256.056 435.069 256.056 435.069 361.012 237.812Z"
fill="white"
/>
<path
opacity="0.2"
d="M256.044 297.764L361 237.812C256.045 435.069 256.044 435.069 256.044 435.069C256.044 392.108 256.044 342.88 256.044 297.764Z"
fill="white"
/>
<path
d="M150.988 237.812L148.122 242.209C115.796 291.798 123.016 356.742 165.48 398.348C190.461 422.826 223.202 435.066 255.944 435.069C255.944 435.069 255.944 435.069 150.988 237.812Z"
fill="white"
/>
<path
opacity="0.6"
d="M255.914 297.764L150.958 237.812C255.914 435.069 255.914 435.069 255.914 435.069C255.914 392.108 255.914 342.88 255.914 297.764Z"
fill="white"
/>
<path opacity="0.2" d="M256.083 163.833V267.233L346.491 215.566L256.083 163.833Z" fill="white" />
<path opacity="0.6" d="M256.056 163.833L165.583 215.565L256.056 267.233V163.833Z" fill="white" />
<path d="M256.056 76.875L165.583 215.599L256.056 163.722V76.875Z" fill="white" />
<path opacity="0.6" d="M256.083 163.706L346.56 215.585L256.083 76.7916V163.706Z" fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,19 +0,0 @@
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
Sentry.init({
dsn: SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
enabled: process.env.NODE_ENV !== 'development',
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})

View File

@ -1,4 +0,0 @@
defaults.url=https://sentry.io/
defaults.org=delphi-mars
defaults.project=mars-v2
cli.executable=../../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli

View File

@ -1,19 +0,0 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
Sentry.init({
dsn: SENTRY_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENV,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
enabled: process.env.NODE_ENV !== 'development',
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
})

View File

@ -10,7 +10,12 @@ import {
TotalDepositResponse,
VaultConfigBaseForAddr,
} from 'types/generated/mars-params/MarsParams.types'
import { ArrayOfMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
import {
ArrayOfMarket,
ArrayOfUserCollateralResponse,
ArrayOfUserDebtResponse,
UserCollateralResponse,
} from 'types/generated/mars-red-bank/MarsRedBank.types'
interface Cache<T> extends Map<string, { data: T | null; timestamp: number }> {}
@ -62,3 +67,5 @@ export const underlyingDebtCache: Cache<string> = new Map()
export const previewDepositCache: Cache<{ vaultAddress: string; amount: string }> = new Map()
export const stakingAprCache: Cache<StakingApr[]> = new Map()
export const assetParamsCache: Cache<AssetParamsBaseForAddr[]> = new Map()
export const userCollateralCache: Cache<ArrayOfUserCollateralResponse> = new Map()
export const userDebtCache: Cache<ArrayOfUserDebtResponse> = new Map()

View File

@ -6,7 +6,9 @@ import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMo
import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.client'
import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client'
import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client'
import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client'
import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
import { getUrl } from 'utils/url'
let _cosmWasmClient: Map<string, CosmWasmClient> = new Map()
let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map()
@ -15,6 +17,7 @@ let _paramsQueryClient: Map<string, MarsParamsQueryClient> = new Map()
let _incentivesQueryClient: Map<string, MarsIncentivesQueryClient> = new Map()
let _swapperOsmosisClient: Map<string, MarsSwapperOsmosisQueryClient> = new Map()
let _perpsClient: Map<string, MarsPerpsQueryClient> = new Map()
let _redBankQueryClient: Map<string, MarsRedBankQueryClient> = new Map()
const getClient = async (rpc: string) => {
try {
@ -32,7 +35,7 @@ const getClient = async (rpc: string) => {
const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.creditManager
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_creditManagerQueryClient.get(key)) {
@ -49,7 +52,7 @@ const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
const getParamsQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.params
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_paramsQueryClient.get(key)) {
@ -66,7 +69,7 @@ const getParamsQueryClient = async (chainConfig: ChainConfig) => {
const getOracleQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.oracle
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_oracleQueryClient.get(key)) {
@ -82,7 +85,7 @@ const getOracleQueryClient = async (chainConfig: ChainConfig) => {
const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => {
try {
const client = await getClient(chainConfig.endpoints.rpc)
const client = await getClient(getUrl(chainConfig.endpoints.rpc))
return new MarsMockVaultQueryClient(client, address)
} catch (error) {
throw error
@ -92,7 +95,7 @@ const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) =>
const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.incentives
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_incentivesQueryClient.get(key)) {
const client = await getClient(rpc)
@ -108,7 +111,7 @@ const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.swapper
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_swapperOsmosisClient.get(key)) {
const client = await getClient(rpc)
@ -124,7 +127,7 @@ const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.perps
const rpc = chainConfig.endpoints.rpc
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_perpsClient.get(key)) {
const client = await getClient(rpc)
@ -137,13 +140,31 @@ const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
}
}
const getRedBankQueryClient = async (chainConfig: ChainConfig) => {
try {
const contract = chainConfig.contracts.redBank
const rpc = getUrl(chainConfig.endpoints.rpc)
const key = rpc + contract
if (!_redBankQueryClient.get(key)) {
const client = await getClient(rpc)
_redBankQueryClient.set(key, new MarsRedBankQueryClient(client, contract))
}
return _redBankQueryClient.get(key)!
} catch (error) {
throw error
}
}
export {
getClient,
getCreditManagerQueryClient,
getIncentivesQueryClient,
getOracleQueryClient,
getParamsQueryClient,
getPerpsQueryClient,
getRedBankQueryClient,
getSwapperQueryClient,
getVaultQueryClient,
getPerpsQueryClient,
}

View File

@ -0,0 +1,41 @@
import { cacheFn, userCollateralCache, userDebtCache } from 'api/cache'
import { getRedBankQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin'
import {
ArrayOfUserCollateralResponse,
ArrayOfUserDebtResponse,
} from 'types/generated/mars-red-bank/MarsRedBank.types'
export default async function getV1Positions(
chainConfig: ChainConfig,
user?: string,
): Promise<Account> {
if (!user) return new Promise((_, reject) => reject('No account Wallet ID found'))
const redBankQueryClient = await getRedBankQueryClient(chainConfig)
const userCollateral: ArrayOfUserCollateralResponse = await cacheFn(
() => redBankQueryClient.userCollaterals({ user: user, limit: 100 }),
userCollateralCache,
`${chainConfig.id}/v1/deposits/${user}`,
)
const userDebt: ArrayOfUserDebtResponse = await cacheFn(
() => redBankQueryClient.userDebts({ user: user, limit: 100 }),
userDebtCache,
`${chainConfig.id}/v1/debts/${user}`,
)
if (userCollateral && userDebt) {
return {
id: user,
debts: userDebt.map((debt) => new BNCoin(debt)),
lends: userCollateral.map((lend) => new BNCoin(lend)),
deposits: [],
vaults: [],
perps: [],
kind: 'default',
}
}
return new Promise((_, reject) => reject('No account found'))
}

View File

@ -10,12 +10,12 @@ import Text from 'components/common/Text'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
interface Props {
account: Account
asset: Asset
title: string
coinBalances: BNCoin[]
@ -29,6 +29,7 @@ interface Props {
export default function AssetAmountSelectActionModal(props: Props) {
const {
account,
asset,
title,
coinBalances,
@ -41,7 +42,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
} = props
const [amount, setAmount] = useState(BN_ZERO)
const maxAmount = BN(coinBalances.find(byDenom(asset.denom))?.amount ?? 0)
const account = useCurrentAccount()
const handleAmountChange = useCallback(
(value: BigNumber) => {
setAmount(value)
@ -54,7 +54,6 @@ export default function AssetAmountSelectActionModal(props: Props) {
onAction(amount, amount.isEqualTo(maxAmount))
}, [amount, maxAmount, onAction])
if (!account) return
return (
<Modal
onClose={onClose}

View File

@ -35,18 +35,9 @@ interface Props {
modal: BorrowModal
}
function getDebtAmount(modal: BorrowModal) {
return BN((modal.marketData as BorrowMarketTableData)?.debt ?? 0).toString()
}
function getAssetLogo(modal: BorrowModal) {
if (!modal.asset) return null
return <AssetImage asset={modal.asset} size={24} />
}
function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) {
return (
<Card className='mt-6'>
<Card className='w-full'>
<div className='flex items-start p-4'>
<InfoCircle className='w-6 mr-2 flex-0' />
<div className='flex flex-col flex-1 gap-1'>
@ -90,7 +81,6 @@ function BorrowModal(props: Props) {
const apy = modal.marketData.apy.borrow
const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id)
const { computeMaxBorrowAmount } = useHealthComputer(account)
const totalDebt = BN(getDebtAmount(modal))
const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO
const markets = useMarkets()
@ -237,7 +227,7 @@ function BorrowModal(props: Props) {
onClose={onClose}
header={
<span className='flex items-center gap-4 px-4'>
{getAssetLogo(modal)}
<AssetImage asset={asset} size={24} />
<Text>
{isRepay ? 'Repay' : 'Borrow'} {asset.symbol}
</Text>
@ -251,14 +241,14 @@ function BorrowModal(props: Props) {
title={formatPercent(modal.marketData.apy.borrow)}
sub={'Borrow Rate APY'}
/>
{totalDebt.isGreaterThan(0) && (
{accountDebt.isGreaterThan(0) && (
<>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={totalDebt.toNumber()}
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
@ -267,7 +257,7 @@ function BorrowModal(props: Props) {
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, totalDebt)}
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
@ -303,59 +293,57 @@ function BorrowModal(props: Props) {
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full min-h-[380px]'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<div className='flex flex-wrap w-full'>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
{isRepay && maxRepayAmount.isZero() && (
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
)}
{isRepay ? (
<>
<Divider className='my-6' />
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
{isRepay && maxRepayAmount.isZero() && (
<RepayNotAvailable asset={asset} repayFromWallet={repayFromWallet} />
)}
{isRepay ? (
<>
<Divider />
<div className='flex items-center w-full'>
<div className='flex flex-wrap flex-1'>
<Text className='w-full mb-1'>Repay from Wallet</Text>
<Text size='xs' className='text-white/50'>
Repay your debt directly from your wallet
</Text>
</div>
<div className='flex flex-wrap items-center justify-end'>
<Switch
name='borrow-to-wallet'
checked={repayFromWallet}
onChange={setRepayFromWallet}
/>
</div>
</>
) : (
<>
<Divider className='my-6' />
<Switch
name='borrow-to-wallet'
checked={repayFromWallet}
onChange={setRepayFromWallet}
/>
</div>
</>
) : (
<>
<Divider />
<div className='flex items-center w-full'>
<div className='flex flex-wrap flex-1'>
<Text className='w-full mb-1'>Receive funds to Wallet</Text>
<Text size='xs' className='text-white/50'>
Your borrowed funds will directly go to your wallet
</Text>
</div>
<div className='flex flex-wrap items-center justify-end'>
<Switch
name='borrow-to-wallet'
checked={borrowToWallet}
onChange={setBorrowToWallet}
/>
</div>
</>
)}
</div>
<Switch
name='borrow-to-wallet'
checked={borrowToWallet}
onChange={setBorrowToWallet}
/>
</div>
</>
)}
<Button
onClick={onConfirmClick}
className='w-full'

View File

@ -23,6 +23,7 @@ interface Props {
}
function LendAndReclaimModal({ currentAccount, config }: Props) {
const account = useCurrentAccount()
const lend = useStore((s) => s.lend)
const reclaim = useStore((s) => s.reclaim)
const { close } = useLendAndReclaimModal()
@ -65,8 +66,11 @@ function LendAndReclaimModal({ currentAccount, config }: Props) {
},
[asset.denom, close, currentAccount.id, isLendAction, lend, reclaim],
)
if (!account) return null
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={data} />}
coinBalances={coinBalances}

View File

@ -10,6 +10,8 @@ import {
LendAndReclaimModalController,
SettingsModal,
UnlockModal,
V1BorrowAndRepay,
V1DepositAndWithdraw,
VaultModal,
WalletAssets,
WithdrawFromVaultsModal,
@ -32,6 +34,8 @@ export default function ModalsContainer() {
<AlertDialogController />
<HlsModal />
<HlsManageModal />
<V1DepositAndWithdraw />
<V1BorrowAndRepay />
</>
)
}

View File

@ -4,11 +4,13 @@ export { default as AlertDialogController } from 'components/Modals/AlertDialog'
export { default as BorrowModal } from 'components/Modals/BorrowModal'
export { default as FundAndWithdrawModal } from 'components/Modals/FundWithdraw'
export { default as GetStartedModal } from 'components/Modals/GetStartedModal'
export { default as HlsModal } from 'components/Modals/HLS'
export { default as HlsManageModal } from 'components/Modals/HLS/Manage'
export { default as LendAndReclaimModalController } from 'components/Modals/LendAndReclaim'
export { default as SettingsModal } from 'components/Modals/Settings'
export { default as UnlockModal } from 'components/Modals/Unlock'
export { default as VaultModal } from 'components/Modals/Vault'
export { default as WalletAssets } from 'components/Modals/WalletAssets'
export { default as WithdrawFromVaultsModal } from 'components/Modals/WithdrawFromVaultsModal'
export { default as HlsModal } from 'components/Modals/HLS'
export { default as HlsManageModal } from 'components/Modals/HLS/Manage'
export { default as V1BorrowAndRepay } from 'components/Modals/v1/V1BorrowAndRepay'
export { default as V1DepositAndWithdraw } from 'components/Modals/v1/V1DepositAndWithdraw'

View File

@ -0,0 +1,179 @@
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState } from 'react'
import Modal from 'components/Modals/Modal'
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
import Button from 'components/common/Button'
import Card from 'components/common/Card'
import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight } from 'components/common/Icons'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useHealthComputer from 'hooks/useHealthComputer'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { formatPercent } from 'utils/formatters'
import { getDebtAmountWithInterest } from 'utils/tokens'
interface Props {
account: Account
}
export default function Borrow(props: Props) {
const { account } = props
const modal = useStore((s) => s.v1BorrowAndRepayModal)
const baseAsset = useBaseAsset()
const [amount, setAmount] = useState(BN_ZERO)
const v1Action = useStore((s) => s.v1Action)
const asset = modal?.data.asset ?? baseAsset
const [max, setMax] = useState(BN_ZERO)
const { simulateBorrow } = useUpdatedAccount(account)
const apy = modal?.data.apy.borrow ?? 0
const { computeMaxBorrowAmount } = useHealthComputer(account)
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
const accountDebtWithInterest = useMemo(
() => getDebtAmountWithInterest(accountDebt, apy),
[accountDebt, apy],
)
const close = useCallback(() => {
setAmount(BN_ZERO)
useStore.setState({ v1BorrowAndRepayModal: null })
}, [setAmount])
const onConfirmClick = useCallback(() => {
v1Action('borrow', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
close()
}, [v1Action, asset, amount, close])
const handleChange = useCallback(
(newAmount: BigNumber) => {
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
},
[amount, setAmount],
)
const onDebounce = useCallback(() => {
const borrowCoin = BNCoin.fromDenomAndBigNumber(
asset.denom,
amount.isGreaterThan(max) ? max : amount,
)
simulateBorrow('wallet', borrowCoin)
}, [amount, max, asset, simulateBorrow])
const maxBorrow = useMemo(() => {
const maxBorrowAmount = computeMaxBorrowAmount(asset.denom, 'wallet')
return BigNumber.min(maxBorrowAmount, modal?.data.liquidity || 0)
}, [asset.denom, computeMaxBorrowAmount, modal?.data.liquidity])
useEffect(() => {
if (maxBorrow.isEqualTo(max)) return
setMax(maxBorrow)
}, [account, maxBorrow, max])
useEffect(() => {
if (amount.isLessThanOrEqualTo(max)) return
handleChange(max)
setAmount(max)
}, [amount, max, handleChange])
if (!modal) return null
return (
<Modal
onClose={close}
header={
<span className='flex items-center gap-4 px-4'>
<AssetImage asset={modal.data.asset} size={24} />
<Text>{`Borrow ${asset.symbol} from the Red Bank`}</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
{accountDebt.isGreaterThan(0) && (
<>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
suffix: ` ${asset.symbol}`,
}}
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Total Borrowed
</Text>
</div>
</>
)}
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={modal.data.liquidity.toNumber() ?? 0}
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
animate
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data.liquidity ?? BN_ZERO)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Liquidity available
</Text>
</div>
</div>
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
<Divider />
<Button
onClick={onConfirmClick}
className='w-full'
disabled={amount.isZero()}
text={`Borrow ${asset.symbol}`}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummaryInModal account={account} />
</div>
</Modal>
)
}

View File

@ -0,0 +1,82 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import WalletBridges from 'components/Wallet/WalletBridges'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { defaultFee } from 'utils/constants'
import { BN } from 'utils/helpers'
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
interface Props {
account: Account
}
export default function Deposit(props: Props) {
const { account } = props
const baseAsset = useBaseAsset()
const modal = useStore((s) => s.v1DepositAndWithdrawModal)
const address = useStore((s) => s.address)
const asset = modal?.data.asset ?? baseAsset
const [fundingAsset, setFundingAsset] = useState<BNCoin>(
BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO),
)
const { data: walletBalances } = useWalletBalances(address)
const { simulateDeposits } = useUpdatedAccount(account)
const balance = useCurrentWalletBalance(asset.denom)
const v1Action = useStore((s) => s.v1Action)
const baseBalance = useMemo(
() => walletBalances.find(byDenom(baseAsset.denom))?.amount ?? '0',
[walletBalances, baseAsset],
)
const close = useCallback(() => {
useStore.setState({ v1DepositAndWithdrawModal: null })
}, [])
const handleClick = useCallback(async () => {
v1Action('deposit', fundingAsset)
close()
}, [v1Action, fundingAsset, close])
useEffect(() => {
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) {
useStore.setState({ focusComponent: { component: <WalletBridges /> } })
}
}, [baseBalance])
const onDebounce = useCallback(() => {
simulateDeposits('lend', [fundingAsset])
}, [fundingAsset, simulateDeposits])
const handleAmountChange = useCallback(
(value: BigNumber) => {
setFundingAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value))
},
[asset.denom],
)
if (!modal) return
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={modal.data} />}
coinBalances={balance ? [BNCoin.fromCoin(balance)] : []}
actionButtonText={`Deposit ${asset.symbol}`}
title={`Deposit ${asset.symbol} into the Red Bank`}
onClose={close}
onAction={handleClick}
onChange={handleAmountChange}
onDebounce={onDebounce}
/>
)
}

View File

@ -0,0 +1,208 @@
import BigNumber from 'bignumber.js'
import { useCallback, useEffect, useMemo, useState } from 'react'
import Modal from 'components/Modals/Modal'
import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal'
import Button from 'components/common/Button'
import Card from 'components/common/Card'
import DisplayCurrency from 'components/common/DisplayCurrency'
import Divider from 'components/common/Divider'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight, InfoCircle } from 'components/common/Icons'
import Text from 'components/common/Text'
import TitleAndSubCell from 'components/common/TitleAndSubCell'
import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider'
import AssetImage from 'components/common/assets/AssetImage'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useMarkets from 'hooks/markets/useMarkets'
import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { formatPercent } from 'utils/formatters'
import { BN } from 'utils/helpers'
import { getDebtAmountWithInterest } from 'utils/tokens'
interface Props {
account: Account
}
function RepayNotAvailable(props: { asset: Asset }) {
return (
<Card className='w-full'>
<div className='flex items-start p-4'>
<InfoCircle className='w-6 mr-2 flex-0' />
<div className='flex flex-col flex-1 gap-1'>
<Text size='sm'>No funds for repay</Text>
<Text
size='xs'
className='text-white/40'
>{`Unfortunately you don't have any ${props.asset.symbol} in your Wallet to repay the debt.`}</Text>
</div>
</div>
</Card>
)
}
export default function Repay(props: Props) {
const { account } = props
const modal = useStore((s) => s.v1BorrowAndRepayModal)
const baseAsset = useBaseAsset()
const asset = modal?.data.asset ?? baseAsset
const [amount, setAmount] = useState(BN_ZERO)
const balance = useCurrentWalletBalance(asset.denom)
const v1Action = useStore((s) => s.v1Action)
const [max, setMax] = useState(BN_ZERO)
const { simulateRepay } = useUpdatedAccount(account)
const apy = modal?.data.apy.borrow ?? 0
const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO
const markets = useMarkets()
const accountDebtWithInterest = useMemo(
() => getDebtAmountWithInterest(accountDebt, apy),
[accountDebt, apy],
)
const overpayExeedsCap = useMemo(() => {
const marketAsset = markets.find((market) => market.asset.denom === asset.denom)
if (!marketAsset) return
const overpayAmount = accountDebtWithInterest.minus(accountDebt)
const marketCapAfterOverpay = marketAsset.cap.used.plus(overpayAmount)
return marketAsset.cap.max.isLessThanOrEqualTo(marketCapAfterOverpay)
}, [markets, asset.denom, accountDebt, accountDebtWithInterest])
const maxRepayAmount = useMemo(() => {
const maxBalance = BN(balance?.amount ?? 0)
return BigNumber.min(maxBalance, overpayExeedsCap ? accountDebt : accountDebtWithInterest)
}, [accountDebtWithInterest, overpayExeedsCap, accountDebt, balance?.amount])
const close = useCallback(() => {
setAmount(BN_ZERO)
useStore.setState({ v1BorrowAndRepayModal: null })
}, [setAmount])
const onConfirmClick = useCallback(() => {
v1Action('repay', BNCoin.fromDenomAndBigNumber(asset.denom, amount))
close()
}, [v1Action, asset, amount, close])
const handleChange = useCallback(
(newAmount: BigNumber) => {
if (!amount.isEqualTo(newAmount)) setAmount(newAmount)
},
[amount, setAmount],
)
const onDebounce = useCallback(() => {
const repayCoin = BNCoin.fromDenomAndBigNumber(
asset.denom,
amount.isGreaterThan(accountDebt) ? accountDebt : amount,
)
simulateRepay(repayCoin, true)
}, [amount, accountDebt, asset, simulateRepay])
useEffect(() => {
if (maxRepayAmount.isEqualTo(max)) return
setMax(maxRepayAmount)
}, [max, maxRepayAmount])
useEffect(() => {
if (amount.isLessThanOrEqualTo(max)) return
handleChange(max)
setAmount(max)
}, [amount, max, handleChange])
if (!modal) return null
return (
<Modal
onClose={close}
header={
<span className='flex items-center gap-4 px-4'>
<AssetImage asset={modal.data.asset} size={24} />
<Text>
{'Repay'} {asset.symbol}
</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3 px-6 py-4 border-b border-white/5 gradient-header'>
<TitleAndSubCell title={formatPercent(apy)} sub={'Borrow Rate APY'} />
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={accountDebt.toNumber()}
options={{
decimals: asset.decimals,
abbreviated: false,
suffix: ` ${asset.symbol}`,
}}
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, accountDebt)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Total Borrowed
</Text>
</div>
<div className='h-100 w-[1px] bg-white/10' />
<div className='flex flex-col gap-0.5'>
<div className='flex gap-2'>
<FormattedNumber
className='text-xs'
amount={modal.data?.liquidity.toNumber() ?? 0}
options={{ decimals: asset.decimals, abbreviated: true, suffix: ` ${asset.symbol}` }}
animate
/>
<DisplayCurrency
className='text-xs'
coin={BNCoin.fromDenomAndBigNumber(asset.denom, modal.data?.liquidity ?? BN_ZERO)}
parentheses
/>
</div>
<Text size='xs' className='text-white/50' tag='span'>
Liquidity available
</Text>
</div>
</div>
<div className='flex items-start flex-1 gap-6 p-6'>
<Card
className='flex flex-1 p-4 bg-white/5'
contentClassName='gap-6 flex flex-col justify-between h-full'
>
<TokenInputWithSlider
asset={asset}
onChange={handleChange}
onDebounce={onDebounce}
amount={amount}
max={max}
disabled={max.isZero()}
className='w-full'
maxText='Max'
warningMessages={[]}
/>
<Divider />
{maxRepayAmount.isZero() && <RepayNotAvailable asset={asset} />}
<Button
onClick={onConfirmClick}
className='w-full'
disabled={amount.isZero()}
text={`Repay ${asset.symbol}`}
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummaryInModal account={account} />
</div>
</Modal>
)
}

View File

@ -0,0 +1,15 @@
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
import Borrow from 'components/Modals/v1/Borrow'
import Repay from 'components/Modals/v1/Repay'
export default function V1BorrowAndRepayModal() {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const modal = useStore<V1BorrowAndRepayModal | null>((s) => s.v1BorrowAndRepayModal)
const isBorrow = modal?.type === 'borrow'
if (!modal || !account) return null
if (isBorrow) return <Borrow account={account} />
return <Repay account={account} />
}

View File

@ -0,0 +1,15 @@
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
import Deposit from 'components/Modals/v1/Deposit'
import Withdraw from 'components/Modals/v1/Withdraw'
export default function V1DepositAndWithdraw() {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const modal = useStore<V1DepositAndWithdrawModal | null>((s) => s.v1DepositAndWithdrawModal)
const isDeposit = modal?.type === 'deposit'
if (!modal || !account) return null
if (isDeposit) return <Deposit account={account} />
return <Withdraw account={account} />
}

View File

@ -0,0 +1,66 @@
import { useCallback, useState } from 'react'
import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal'
import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader'
import { BN_ZERO } from 'constants/math'
import useBaseAsset from 'hooks/assets/useBasetAsset'
import useHealthComputer from 'hooks/useHealthComputer'
import { useUpdatedAccount } from 'hooks/useUpdatedAccount'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
interface Props {
account: Account
}
export default function Withdraw(props: Props) {
const { account } = props
const baseAsset = useBaseAsset()
const modal = useStore((s) => s.v1DepositAndWithdrawModal)
const asset = modal?.data.asset ?? baseAsset
const [withdrawAsset, setWithdrawAsset] = useState<BNCoin>(
BNCoin.fromDenomAndBigNumber(modal?.data.asset.denom ?? baseAsset.denom, BN_ZERO),
)
const { computeMaxWithdrawAmount } = useHealthComputer(account)
const maxWithdrawAmount = computeMaxWithdrawAmount(asset.denom)
const { simulateWithdraw } = useUpdatedAccount(account)
const balance = BNCoin.fromDenomAndBigNumber(asset.denom, maxWithdrawAmount)
const v1Action = useStore((s) => s.v1Action)
const close = useCallback(() => {
useStore.setState({ v1DepositAndWithdrawModal: null })
}, [])
const handleClick = useCallback(async () => {
v1Action('withdraw', withdrawAsset)
close()
}, [v1Action, withdrawAsset, close])
const onDebounce = useCallback(() => {
simulateWithdraw(false, withdrawAsset)
}, [withdrawAsset, simulateWithdraw])
const handleAmountChange = useCallback(
(value: BigNumber) => {
setWithdrawAsset(BNCoin.fromDenomAndBigNumber(asset.denom, value))
},
[asset.denom],
)
if (!modal) return
return (
<AssetAmountSelectActionModal
account={account}
asset={asset}
contentHeader={<DetailsHeader data={modal.data} />}
coinBalances={[balance]}
actionButtonText={`Withdraw ${asset.symbol}`}
title={`Withdraw ${asset.symbol} from the Red Bank`}
onClose={close}
onAction={handleClick}
onChange={handleAmountChange}
onDebounce={onDebounce}
/>
)
}

View File

@ -7,8 +7,8 @@ import Text from 'components/common/Text'
import { TextLink } from 'components/common/TextLink'
import { generateToastContent } from 'components/common/Toaster'
import useTransactions from 'hooks/localStorage/useTransactions'
import useStore from 'store'
import useChainConfig from 'hooks/useChainConfig'
import useStore from 'store'
export default function RecentTransactions() {
const address = useStore((s) => s.address)
@ -47,7 +47,9 @@ export default function RecentTransactions() {
key={hash}
>
<div className='flex items-start justify-between w-full pb-2'>
<Text className='flex font-bold'>Credit Account {accountId}</Text>
<Text className='flex font-bold'>
{accountId === address ? 'Red Bank' : `Credit Account ${accountId}`}
</Text>
<Text size='sm' className='text-white/70'>
{moment.unix(timestamp).format('lll')}
</Text>

View File

@ -15,6 +15,7 @@ interface Props {
color?: ButtonProps['color']
variant?: ButtonProps['variant']
size?: ButtonProps['size']
short?: boolean
}
export default function WalletConnectButton(props: Props) {

View File

@ -10,6 +10,7 @@ import useChainConfig from 'hooks/useChainConfig'
import useCurrentWallet from 'hooks/useCurrentWallet'
import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { getUrl } from 'utils/url'
interface Props {
providerId?: string
@ -53,7 +54,7 @@ export default function WalletConnecting(props: Props) {
setIsConnecting(true)
try {
const response = await connect({ extensionProviderId, chainId: chainConfig.id })
const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc)
const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc))
const walletClient: WalletClient = {
broadcast,
cosmWasmClient: cosmClient,
@ -137,7 +138,7 @@ export default function WalletConnecting(props: Props) {
setIsConnecting(true)
try {
await mobileConnect({ mobileProviderId, chainId: chainConfig.id })
const cosmClient = await CosmWasmClient.connect(chainConfig.endpoints.rpc)
const cosmClient = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc))
const walletClient: WalletClient = {
broadcast,
cosmWasmClient: cosmClient,

View File

@ -29,7 +29,7 @@ function FetchLoading() {
function Content() {
const address = useStore((s) => s.address)
const [searchParams] = useSearchParams()
const isV1 = useStore((s) => s.isV1)
const { address: urlAddress } = useParams()
const urlAccountId = useAccountId()
const navigate = useNavigate()
@ -62,7 +62,7 @@ function Content() {
) {
const currentAccountIsHLS = urlAccountId && !accountIds.includes(urlAccountId)
const currentAccount = currentAccountIsHLS || !urlAccountId ? accountIds[0] : urlAccountId
navigate(getRoute(page, searchParams, address, currentAccount))
navigate(getRoute(page, searchParams, address, isV1 ? undefined : currentAccount))
useStore.setState({ balances: walletBalances, focusComponent: null })
}
}, [
@ -75,11 +75,12 @@ function Content() {
urlAddress,
urlAccountId,
searchParams,
isV1,
])
if (isLoadingAccounts || isLoadingBalances) return <FetchLoading />
if (BN(baseBalance).isLessThan(defaultFee.amount[0].amount)) return <WalletBridges />
if (accountIds && accountIds.length === 0) return <AccountCreateFirst />
if (accountIds && accountIds.length === 0 && !isV1) return <AccountCreateFirst />
return null
}

View File

@ -75,6 +75,7 @@ export default function AccountBalancesTable(props: Props) {
},
})
}}
short
/>
</div>
</ConditionalWrapper>

View File

@ -37,6 +37,7 @@ import {
export default function AccountDetailsController() {
const address = useStore((s) => s.address)
const isHLS = useStore((s) => s.isHLS)
const isV1 = useStore((s) => s.isV1)
const { data: _, isLoading } = useAccounts('default', address)
const { data: accountIds } = useAccountIds(address, false, true)
@ -45,10 +46,11 @@ export default function AccountDetailsController() {
const account = useCurrentAccount()
const focusComponent = useStore((s) => s.focusComponent)
const isOwnAccount = accountId && accountIds?.includes(accountId)
const hideAccountDetails = !address || focusComponent || !isOwnAccount || isHLS || isV1
const isLoadingAccountDetails = (isLoading && accountId && !focusComponent) || !account
if (!address || focusComponent || !isOwnAccount || isHLS) return null
if ((isLoading && accountId && !focusComponent) || !account) return <Skeleton />
if (hideAccountDetails) return null
if (isLoadingAccountDetails) return <Skeleton />
return <AccountDetails account={account} />
}

View File

@ -1,19 +1,19 @@
import ActiveBorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable'
import AvailableBorrowingsTable from 'components/borrow/Table/AvailableBorrowingsTable'
import DepositedBorrowingsTable from 'components/borrow/Table/DepositedBorrowingsTable'
import useBorrowMarketAssetsTableData from 'components/borrow/Table/useBorrowMarketAssetsTableData'
import { BN_ZERO } from 'constants/math'
import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets'
export default function Borrowings() {
const data = useBorrowMarketAssetsTableData()
const { accountBorrowedAssets, availableAssets, allAssets } = useBorrowMarketAssetsTableData()
if (!data?.allAssets?.length) {
if (!allAssets?.length) {
return <Fallback />
}
return (
<>
<DepositedBorrowingsTable data={data.accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={data.availableAssets} isLoading={false} />
<ActiveBorrowingsTable data={accountBorrowedAssets} isLoading={false} />
<AvailableBorrowingsTable data={availableAssets} isLoading={false} />
</>
)
}

View File

@ -1,18 +1,20 @@
import { Row } from '@tanstack/react-table'
import { useCallback } from 'react'
import { DEBT_VALUE_META } from 'components/borrow/Table/Columns/DebtValue'
import { NAME_META } from 'components/borrow/Table/Columns/Name'
import useDepositedColumns from 'components/borrow/Table/Columns/useDepositedColumns'
import useBorrowingsColumns from 'components/borrow/Table/Columns/useActiveColumns'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
type Props = {
data: BorrowMarketTableData[]
isLoading: boolean
v1?: boolean
}
export default function DepositedBorrowingsTable(props: Props) {
const columns = useDepositedColumns()
export default function ActiveBorrowingsTable(props: Props) {
const columns = useBorrowingsColumns({ v1: props.v1 })
const renderExpanded = useCallback((row: Row<BorrowMarketTableData>) => {
return <MarketDetails row={row} type='borrow' />
@ -22,10 +24,17 @@ export default function DepositedBorrowingsTable(props: Props) {
return (
<Table
title='Borrowed Assets'
title={props.v1 ? 'Borrowings' : 'Borrowed Assets'}
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
initialSorting={
props.v1
? [
{ id: DEBT_VALUE_META.id, desc: true },
{ id: NAME_META.id, desc: false },
]
: [{ id: NAME_META.id, desc: false }]
}
renderExpanded={renderExpanded}
/>
)

View File

@ -13,7 +13,7 @@ type Props = {
}
export default function AvailableBorrowingsTable(props: Props) {
const columns = useAvailableColumns()
const columns = useAvailableColumns({ v1: false })
const renderExpanded = useCallback(
(row: Row<BorrowMarketTableData>, _: TanstackTable<BorrowMarketTableData>) => {

View File

@ -54,6 +54,7 @@ export default function BorrowButton(props: Props) {
e.stopPropagation()
}}
text='Borrow'
short
/>
</ConditionalWrapper>
</div>

View File

@ -1,36 +0,0 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/common/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
import { byDenom } from 'utils/array'
export const DEBT_META = {
accessorKey: 'debt',
header: 'Debt',
}
export const debtSortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const assetA = a.original.asset
const assetB = b.original.asset
if (!a.original.accountDebt || !b.original.accountDebt) return 0
const debtA = a.original.accountDebt.shiftedBy(-assetA.decimals)
const debtB = b.original.accountDebt.shiftedBy(-assetB.decimals)
return debtA.minus(debtB).toNumber()
}
interface Props {
data: BorrowMarketTableData
}
export default function Debt(props: Props) {
const marketAssets = useMarketEnabledAssets()
const asset = marketAssets.find(byDenom(props.data.asset.denom))
if (!asset) return null
return <AmountAndValue asset={asset} amount={props.data?.accountDebt ?? BN_ZERO} />
}

View File

@ -0,0 +1,30 @@
import { Row } from '@tanstack/react-table'
import AmountAndValue from 'components/common/AmountAndValue'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEBT_VALUE_META = {
id: 'accountDebtValue',
accessorKey: 'accountDebtValue',
header: 'Debt',
}
export const debtSortingFn = (
a: Row<BorrowMarketTableData>,
b: Row<BorrowMarketTableData>,
): number => {
const debtValueA = BN(a.original?.accountDebtValue ?? 0)
const debtValueB = BN(b.original?.accountDebtValue ?? 0)
return debtValueA.minus(debtValueB).toNumber()
}
interface Props {
asset: Asset
debtAmount?: BigNumber
}
export default function DebtValue(props: Props) {
return (
<AmountAndValue asset={props.asset} amount={props.debtAmount ? props.debtAmount : BN_ZERO} />
)
}

View File

@ -48,7 +48,7 @@ export default function Manage(props: Props) {
if (!address) return null
return (
<div className='flex justify-end z-10'>
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)

View File

@ -5,6 +5,7 @@ export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'sy
interface Props {
data: BorrowMarketTableData
v1?: boolean
}
export default function Name(props: Props) {
@ -12,7 +13,11 @@ export default function Name(props: Props) {
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
<TitleAndSubCell
title={asset.symbol}
sub={props.v1 ? '' : asset.name}
className='text-left min-w-15'
/>
</div>
)
}

View File

@ -3,24 +3,34 @@ import { useMemo } from 'react'
import BorrowRate, { BORROW_RATE_META } from 'components/borrow/Table/Columns/BorrowRate'
import Chevron, { CHEVRON_META } from 'components/borrow/Table/Columns/Chevron'
import Debt, { DEBT_META, debtSortingFn } from 'components/borrow/Table/Columns/Debt'
import DebtValue, {
DEBT_VALUE_META,
debtSortingFn,
} from 'components/borrow/Table/Columns/DebtValue'
import Liquidity, {
LIQUIDITY_META,
liquiditySortingFn,
} from 'components/borrow/Table/Columns/Liquidity'
import Manage, { MANAGE_META } from 'components/borrow/Table/Columns/Manage'
import Name, { NAME_META } from 'components/borrow/Table/Columns/Name'
import Action from 'components/v1/Table/borrowings/Columns/Action'
export default function useDepositedColumns() {
interface Props {
v1?: boolean
}
export default function useActiveColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
cell: ({ row }) => <Name data={row.original} v1={props.v1} />,
},
{
...DEBT_META,
cell: ({ row }) => <Debt data={row.original} />,
...DEBT_VALUE_META,
cell: ({ row }) => (
<DebtValue asset={row.original.asset} debtAmount={row.original.accountDebtAmount} />
),
sortingFn: debtSortingFn,
},
{
@ -34,12 +44,13 @@ export default function useDepositedColumns() {
},
{
...MANAGE_META,
cell: ({ row }) => <Manage data={row.original} />,
cell: ({ row }) =>
props.v1 ? <Action data={row.original} /> : <Manage data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}, [props.v1])
}

View File

@ -10,12 +10,16 @@ import Liquidity, {
} from 'components/borrow/Table/Columns/Liquidity'
import Name, { NAME_META } from 'components/borrow/Table/Columns/Name'
export default function useAvailableColumns() {
interface Props {
v1?: boolean
}
export default function useAvailableColumns(props: Props) {
return useMemo<ColumnDef<BorrowMarketTableData>[]>(() => {
return [
{
...NAME_META,
cell: ({ row }) => <Name data={row.original} />,
cell: ({ row }) => <Name data={row.original} v1={props.v1} />,
},
{
...BORROW_RATE_META,
@ -35,5 +39,5 @@ export default function useAvailableColumns() {
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [])
}, [props.v1])
}

View File

@ -1,11 +1,14 @@
import { useMemo } from 'react'
import { BN_ZERO } from 'constants/math'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useMarkets from 'hooks/markets/useMarkets'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
export default function useBorrowMarketAssetsTableData() {
const account = useCurrentAccount()
const markets = useMarkets()
const { convertAmount } = useDisplayCurrencyPrice()
return useMemo((): {
accountBorrowedAssets: BorrowMarketTableData[]
@ -18,15 +21,21 @@ export default function useBorrowMarketAssetsTableData() {
markets
.filter((market) => market.borrowEnabled)
.forEach((market) => {
const debt = account?.debts?.find((debt) => debt.denom === market.asset.denom)
const amount =
account?.debts?.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO
const value = amount ? convertAmount(market.asset, amount) : undefined
const borrowMarketAsset: BorrowMarketTableData = {
...market,
accountDebt: debt?.amount,
accountDebtAmount: amount,
accountDebtValue: value,
}
if (borrowMarketAsset.accountDebtAmount?.isZero()) {
availableAssets.push(borrowMarketAsset)
} else {
accountBorrowedAssets.push(borrowMarketAsset)
}
;(borrowMarketAsset.accountDebt ? accountBorrowedAssets : availableAssets).push(
borrowMarketAsset,
)
})
return {
@ -34,5 +43,5 @@ export default function useBorrowMarketAssetsTableData() {
availableAssets,
allAssets: [...accountBorrowedAssets, ...availableAssets],
}
}, [account?.debts, markets])
}, [account?.debts, markets, convertAmount])
}

View File

@ -15,19 +15,31 @@ export default function Background() {
)
const { pathname } = useLocation()
const page = getPage(pathname)
const isHLS = useMemo(() => page.split('-')[0] === 'hls', [page])
const [isHLS, isV1] = useMemo(() => [page.split('-')[0] === 'hls', page === 'v1'], [page])
useEffect(() => {
useStore.setState({ isHLS })
}, [isHLS])
useStore.setState({ isHLS: isHLS, isV1: isV1 })
}, [isHLS, isV1])
const [primaryOrbClassName, secondaryOrbClassName, tertiaryOrbClassName, bodyClassName] =
useMemo(() => {
if (isHLS) {
return ['bg-orb-primary-hls', 'bg-orb-secondary-hls', 'bg-orb-tertiary-hls', 'bg-body-hls']
}
if (isV1) {
return ['bg-transparent', 'bg-transparent', 'bg-transparent', 'bg-body bg-v1 blur-[2px]']
}
return ['bg-orb-primary', 'bg-orb-secondary', 'bg-orb-tertiary', 'bg-body']
}, [isHLS, isV1])
return (
<div
className={classNames(
'fixed inset-0',
'w-full h-full',
'overflow-hidden pointer-events-none background ',
isHLS ? 'bg-body-hls' : 'bg-body',
'overflow-hidden pointer-events-none background',
bodyClassName,
!reduceMotion && 'transition-bg duration-1000 delay-300',
)}
>
@ -39,7 +51,7 @@ export default function Background() {
'max-h-[500px] max-w-[500px]',
'left-[-10vw] top-[-10vw]',
'blur-orb-primary',
isHLS ? ' bg-orb-primary-hls' : 'bg-orb-primary',
primaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_120s_ease-in-out_infinite_2s]',
!reduceMotion && 'transition-bg duration-1000 delay-300',
@ -53,7 +65,7 @@ export default function Background() {
'max-h-[1000px] max-w-[1000px]',
'bottom-[-20vw] right-[-10vw]',
'blur-orb-secondary',
isHLS ? ' bg-orb-secondary-hls' : 'bg-orb-secondary',
secondaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-30',
!reduceMotion && 'transition-bg duration-1000 delay-300',
)}
@ -66,7 +78,7 @@ export default function Background() {
'max-h-[600px] max-w-[600px]',
'right-[-4vw] top-[-10vw]',
'blur-orb-tertiary ',
isHLS ? ' bg-orb-tertiary-hls' : 'bg-orb-tertiary',
tertiaryOrbClassName,
'translate-x-0 translate-y-0 rounded-full opacity-20',
!reduceMotion && 'animate-[float_180s_ease-in_infinite]',
!reduceMotion && 'transition-bg duration-1000 delay-300',

View File

@ -9,10 +9,15 @@ import useAccountIds from 'hooks/accounts/useAccountIds'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
export default function ActionButton(props: ButtonProps) {
const { className, color, variant, size } = props
interface Props extends ButtonProps {
short?: boolean
}
export default function ActionButton(props: Props) {
const { className, color, variant, size, short } = props
const defaultProps = { className, color, variant, size }
const address = useStore((s) => s.address)
const isV1 = useStore((s) => s.isV1)
const { data: accountIds } = useAccountIds(address || '')
const selectedAccountId = useAccountId()
@ -21,7 +26,8 @@ export default function ActionButton(props: ButtonProps) {
useStore.setState({ focusComponent: { component: <AccountCreateFirst /> } })
}, [])
if (!address) return <WalletConnectButton {...defaultProps} />
if (!address)
return <WalletConnectButton {...defaultProps} textOverride={short ? 'Connect' : undefined} />
if (accountIds && accountIds.length === 0) {
return (
@ -34,7 +40,7 @@ export default function ActionButton(props: ButtonProps) {
)
}
if (!selectedAccountId) {
if (!selectedAccountId && !isV1) {
return (
<Button
text='Select Account'

View File

@ -1,10 +1,12 @@
import { TextLink } from 'components/common/TextLink'
import { DocURL } from 'types/enums/docURL'
import useStore from 'store'
import packageInfo from '../../../package.json'
export default function Footer() {
const version = `v${packageInfo.version}`
const isV1 = useStore((s) => s.isV1)
const version = isV1 ? `v${packageInfo.v1version}` : `v${packageInfo.version}`
const flatVersion = packageInfo.version.split('.').join('')
return (
<footer className='flex items-center justify-center w-full h-6 -mt-6'>

View File

@ -17,7 +17,7 @@ import useStore from 'store'
interface Props {
text: string | ReactNode
children?: ReactNode
bg: 'borrow' | 'lend' | 'farm' | 'portfolio' | 'hls-farm' | 'hls-staking'
bg: Page
}
function IntroBackground(props: { bg: Props['bg'] }) {

View File

@ -23,7 +23,7 @@ export default function MarketDetails({ row, type }: Props) {
symbol: displayCurrencySymbol,
} = useDisplayCurrencyPrice()
const { asset, ltv, cap, liquidity, deposits, debt } = row.original
const { asset, ltv, deposits, debt } = row.original
const details: Detail[] = useMemo(() => {
const isDollar = displayCurrencySymbol === '$'
@ -118,7 +118,7 @@ export default function MarketDetails({ row, type }: Props) {
<TitleAndSubCell
key={index}
className='text-center'
containerClassName='m-5 ml-10 mr-10 space-y-1'
containerClassName='m-5 mx-auto space-y-1'
title={
<FormattedNumber
className='text-xs text-center'

View File

@ -66,7 +66,7 @@ export default function Row<T>(props: Props<T>) {
spacingClassName ?? 'px-3 py-4',
type && type !== 'strategies' && isSymbolOrName && 'border-l',
type && type !== 'strategies' && getBorderColor(type, cell.row.original as any),
cell.column.columnDef.meta?.className ?? 'w-min',
cell.column.columnDef.meta?.className,
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}

View File

@ -76,7 +76,6 @@ export default function Table<T>(props: Props<T>) {
props.spacingClassName ?? 'px-4 py-3',
header.column.getCanSort() && 'hover:cursor-pointer',
header.id === 'symbol' || header.id === 'name' ? 'text-left' : 'text-right',
'w-min',
header.column.columnDef.meta?.className,
)}
>

View File

@ -11,11 +11,11 @@ import { TextLink } from 'components/common/TextLink'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useChainConfig from 'hooks/useChainConfig'
import useTransactionStore from 'hooks/useTransactionStore'
import useStore from 'store'
import { formatAmountWithSymbol } from 'utils/formatters'
import { BN } from 'utils/helpers'
import useChainConfig from 'hooks/useChainConfig'
const toastBodyClasses = classNames(
'flex flex-wrap w-full group/transaction',
@ -99,6 +99,13 @@ export default function Toaster() {
if (!isError && toast.accountId) addTransaction(toast)
const generalMessage = isError ? 'Transaction failed!' : 'Transaction completed successfully!'
const showDetailElement = !!(!details && toast.hash)
const address = useStore.getState().address
let target: string
if (!isError) {
target = toast.accountId === address ? 'Red Bank' : `Credit Account ${toast.accountId}`
}
const Msg = () => (
<div className='relative flex flex-wrap w-full m-0 isolate'>
<div className='flex w-full gap-2 mb-2'>
@ -141,7 +148,7 @@ export default function Toaster() {
)}
>
{!isError && toast.accountId && (
<Text className='mb-1 font-bold text-white'>{`Credit Account ${toast.accountId}`}</Text>
<Text className='mb-1 font-bold text-white'>{target}</Text>
)}
{showDetailElement && toast.message && (
<Text size='sm' className='w-full mb-1 text-white'>

View File

@ -34,6 +34,7 @@ export const Deposit = (props: Props) => {
color='tertiary'
text='Deposit'
leftIcon={<Plus />}
short
/>
</div>
)

View File

@ -1,17 +1,16 @@
import moment from 'moment/moment'
import React, { useCallback, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/common/Icons'
import Loading from 'components/common/Loading'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
import { VaultStatus } from 'types/enums/vault'
import { DEFAULT_SETTINGS } from '../../../../../constants/defaultSettings'
import { LocalStorageKeys } from '../../../../../constants/localStorageKeys'
import useLocalStorage from '../../../../../hooks/localStorage/useLocalStorage'
import useAccountId from '../../../../../hooks/useAccountId'
import useStore from '../../../../../store'
import DropDownButton from '../../../../common/Button/DropDownButton'
export const MANAGE_META = { accessorKey: 'details', enableSorting: false, header: '' }
interface Props {
@ -104,7 +103,7 @@ export default function Manage(props: Props) {
if (!address) return null
return (
<div className='flex justify-end z-10'>
<div className='z-10 flex justify-end'>
<DropDownButton
items={ITEMS}
text='Manage'

View File

@ -2,6 +2,7 @@ import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/earn/farm/Table/Columns/Apy'
import { Deposit, DEPOSIT_META } from 'components/earn/farm/Table/Columns/Deposit'
import DepositCap, {
DEPOSIT_CAP_META,
depositCapSortingFn,
@ -10,8 +11,6 @@ import MaxLTV, { LTV_MAX_META } from 'components/earn/farm/Table/Columns/MaxLTV'
import Name, { NAME_META } from 'components/earn/farm/Table/Columns/Name'
import TVL, { TVL_META } from 'components/earn/farm/Table/Columns/TVL'
import { Deposit, DEPOSIT_META } from './Deposit'
interface Props {
isLoading: boolean
}

View File

@ -1,7 +1,7 @@
import AssetRate from 'components/common/assets/AssetRate'
import Loading from 'components/common/Loading'
export const APY_META = { accessorKey: 'apy.deposit', header: 'APY', meta: { className: 'w-40' } }
export const APY_META = { accessorKey: 'apy.deposit', header: 'APY' }
interface Props {
apy: number

View File

@ -10,6 +10,9 @@ export const DEPOSIT_CAP_META = {
accessorKey: 'marketDepositCap',
header: 'Deposit Cap',
id: 'marketDepositCap',
meta: {
className: 'w-50',
},
}
export const marketDepositCapSortingFn = (

View File

@ -5,6 +5,7 @@ import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export const DEPOSIT_VALUE_META = {
id: 'accountLentValue',
accessorKey: 'accountLentValue',
header: 'Deposited',
}

View File

@ -13,6 +13,9 @@ export const LEND_BUTTON_META = {
accessorKey: 'lend',
enableSorting: false,
header: '',
meta: {
className: 'w-40',
},
}
interface Props {
@ -53,6 +56,7 @@ export default function LendButton(props: Props) {
e.stopPropagation()
}}
text='Lend'
short
/>
</ConditionalWrapper>
</div>

View File

@ -13,6 +13,9 @@ export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: '',
meta: {
className: 'w-40',
},
}
interface Props {
@ -75,7 +78,7 @@ export default function Manage(props: Props) {
if (!address) return null
return (
<div className='flex justify-end z-10'>
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)

View File

@ -4,13 +4,18 @@ import TitleAndSubCell from 'components/common/TitleAndSubCell'
export const NAME_META = { accessorKey: 'asset.symbol', header: 'Asset', id: 'symbol' }
interface Props {
asset: Asset
v1?: boolean
}
export default function Name(props: Props) {
const { asset } = props
return (
<div className='flex items-center flex-1 gap-3'>
<AssetImage asset={asset} size={32} />
<TitleAndSubCell title={asset.symbol} sub={asset.name} className='text-left min-w-15' />
<TitleAndSubCell
title={asset.symbol}
sub={props.v1 ? '' : asset.name}
className='text-left min-w-15'
/>
</div>
)
}

View File

@ -12,6 +12,7 @@ import Name, { NAME_META } from 'components/earn/lend/Table/Columns/Name'
interface Props {
isLoading: boolean
v1?: boolean
}
export default function useAvailableColumns(props: Props) {
@ -19,7 +20,7 @@ export default function useAvailableColumns(props: Props) {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
cell: ({ row }) => <Name asset={row.original.asset} v1={props.v1} />,
},
{
...APY_META,
@ -45,5 +46,5 @@ export default function useAvailableColumns(props: Props) {
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}, [props.isLoading, props.v1])
}

View File

@ -13,9 +13,11 @@ import DepositValue, {
} from 'components/earn/lend/Table/Columns/DepositValue'
import Manage, { MANAGE_META } from 'components/earn/lend/Table/Columns/Manage'
import Name, { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import Action from 'components/v1/Table/deposits/Columns/Action'
interface Props {
isLoading: boolean
v1?: boolean
}
export default function useDepositedColumns(props: Props) {
@ -23,7 +25,7 @@ export default function useDepositedColumns(props: Props) {
return [
{
...NAME_META,
cell: ({ row }) => <Name asset={row.original.asset} />,
cell: ({ row }) => <Name asset={row.original.asset} v1={props.v1} />,
},
{
...DEPOSIT_VALUE_META,
@ -49,12 +51,13 @@ export default function useDepositedColumns(props: Props) {
},
{
...MANAGE_META,
cell: ({ row }) => <Manage data={row.original} />,
cell: ({ row }) =>
props.v1 ? <Action data={row.original} /> : <Manage data={row.original} />,
},
{
...CHEVRON_META,
cell: ({ row }) => <Chevron isExpanded={row.getIsExpanded()} />,
},
]
}, [props.isLoading])
}, [props.isLoading, props.v1])
}

View File

@ -3,16 +3,18 @@ import { useCallback } from 'react'
import MarketDetails from 'components/common/MarketDetails'
import Table from 'components/common/Table'
import { DEPOSIT_VALUE_META } from 'components/earn/lend/Table/Columns/DepositValue'
import { NAME_META } from 'components/earn/lend/Table/Columns/Name'
import useDepositedColumns from 'components/earn/lend/Table/Columns/useDepositedColumns'
type Props = {
data: LendingMarketTableData[]
isLoading: boolean
v1?: boolean
}
export default function DepositedLendsTable(props: Props) {
const columns = useDepositedColumns({ isLoading: props.isLoading })
const columns = useDepositedColumns({ isLoading: props.isLoading, v1: props.v1 })
const renderExpanded = useCallback(
(row: Row<LendingMarketTableData>) => <MarketDetails row={row} type='lend' />,
@ -23,10 +25,17 @@ export default function DepositedLendsTable(props: Props) {
return (
<Table
title='Lent Assets'
title={props.v1 ? 'Deposits' : 'Lent Assets'}
columns={columns}
data={props.data}
initialSorting={[{ id: NAME_META.id, desc: false }]}
initialSorting={
props.v1
? [
{ id: DEPOSIT_VALUE_META.id, desc: true },
{ id: NAME_META.id, desc: false },
]
: [{ id: NAME_META.id, desc: false }]
}
renderExpanded={renderExpanded}
/>
)

View File

@ -1,11 +1,12 @@
import classNames from 'classnames'
import { useCallback, useMemo } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import React, { useCallback, useMemo } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useSWRConfig } from 'swr'
import Button from 'components/common/Button'
import { ExternalLink } from 'components/common/Icons'
import Overlay from 'components/common/Overlay'
import Radio from 'components/common/Radio'
import Text from 'components/common/Text'
import ChainLogo from 'components/common/chain/ChainLogo'
import chains from 'configs/chains'
@ -15,11 +16,52 @@ import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { NETWORK } from 'types/enums/network'
import { ChainInfoID } from 'types/enums/wallet'
import { getPage, getRoute } from 'utils/route'
import { getRoute } from 'utils/route'
const v1Outposts = [
{ chainId: ChainInfoID.Neutron1, name: 'Neutron', url: 'https://neutron.marsprotocol.io' },
{ chainId: ChainInfoID.Osmosis1, name: 'Osmosis', url: 'https://v1.marsprotocol.io' },
interface V1Outpost {
chainId: ChainInfoID
name: string
url: string
network: NETWORK.MAINNET | NETWORK.TESTNET
target: '_blank' | '_self'
}
interface ChainOptionProps {
chainConfig?: ChainConfig
onSelect?: (chain: ChainConfig) => void
active: boolean
outpost?: V1Outpost
}
const v1Outposts: V1Outpost[] = [
{
chainId: ChainInfoID.Neutron1,
name: 'Neutron',
url: 'https://neutron.marsprotocol.io',
network: NETWORK.MAINNET,
target: '_blank',
},
{
chainId: ChainInfoID.Pion1,
name: 'Neutron Testnet',
url: '/v1',
network: NETWORK.TESTNET,
target: '_self',
},
{
chainId: ChainInfoID.Osmosis1,
name: 'Osmosis',
url: '/v1',
network: NETWORK.MAINNET,
target: '_self',
},
{
chainId: ChainInfoID.OsmosisDevnet,
name: 'Osmosis Devnet',
network: NETWORK.TESTNET,
url: '/v1',
target: '_self',
},
]
export default function ChainSelect() {
@ -27,8 +69,8 @@ export default function ChainSelect() {
const chainConfig = useChainConfig()
const { mutate } = useSWRConfig()
const navigate = useNavigate()
const { pathname } = useLocation()
const [searchParams] = useSearchParams()
const isV1 = useStore((s) => s.isV1)
const [_, setCurrentChainId] = useCurrentChainId()
@ -39,20 +81,65 @@ export default function ChainSelect() {
mutate(() => true)
useStore.setState({
chainConfig,
isV1: false,
client: undefined,
address: undefined,
userDomain: undefined,
balances: [],
})
navigate(getRoute(getPage(pathname), searchParams))
navigate(getRoute('trade', searchParams))
},
[setCurrentChainId, setShowMenu, mutate, navigate, pathname, searchParams],
[setCurrentChainId, setShowMenu, mutate, navigate, searchParams],
)
const currentChains = useMemo(() => {
const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET
const ChainOption = (props: ChainOptionProps) => {
const { onSelect, active, outpost, chainConfig } = props
return (
<div
className={classNames(
'w-full px-4 py-3 flex gap-3 group/chain text-white items-center',
active ? 'pointer-events-none' : 'opacity-60 hover:opacity-100',
)}
role='button'
onClick={
onSelect && chainConfig
? () => onSelect(chainConfig)
: () => {
if (chainConfig) {
setCurrentChainId(chainConfig.id)
useStore.setState({
chainConfig,
})
}
window.open(outpost?.url, outpost?.target)
}
}
>
<Radio active={active} className='group-hover/account:opacity-100' />
<Text size='sm'>{outpost ? 'v1' : 'v2'} Outpost</Text>
{outpost && outpost.target !== '_self' && <ExternalLink className='inline w-4 -mb-0.5' />}
</div>
)
}
return Object.entries(chains).filter(([_, chain]) => chain.network === currentNetworkType)
const availableChains = useMemo(() => {
const currentNetworkType = process.env.NEXT_PUBLIC_NETWORK ?? NETWORK.TESTNET
const availableChains: { chainId: ChainInfoID; name: string }[] = []
Object.entries(chains).forEach(([chainId, chainConfig]) => {
if (chainConfig.network !== currentNetworkType) return
availableChains.push({ chainId: chainId as ChainInfoID, name: chainConfig.name })
})
if (currentNetworkType === NETWORK.TESTNET) return availableChains
v1Outposts.forEach((v1Outpost) => {
if (
!availableChains.find((chain) => chain.chainId === v1Outpost.chainId) &&
v1Outpost.network === currentNetworkType
)
availableChains.push({ chainId: v1Outpost.chainId, name: v1Outpost.name })
})
return availableChains
}, [])
return (
@ -63,65 +150,37 @@ export default function ChainSelect() {
color='secondary'
onClick={() => setShowMenu()}
className={classNames('!p-0 w-8 flex items-center justify-center')}
></Button>
<Overlay show={showMenu} setShow={setShowMenu} className='right-0 w-[180px] mt-2'>
<div
className={classNames(
'flex w-full items-center bg-white/5 px-4 py-3',
'border border-transparent border-b-white/10',
)}
>
<Text size='lg' className='font-bold'>
Select Chain
</Text>
</div>
<ul className='w-full px-4 py-3 list-none'>
{currentChains.map(([name, chain]) => (
<li
className={classNames(
'w-full py-2 flex gap-3 group/chain text-white items-center',
chainConfig.name === chain.name
? 'pointer-events-none'
: 'opacity-60 hover:opacity-100',
)}
role='button'
key={name}
onClick={() => selectChain(chain)}
>
<ChainLogo chainID={chain.id} className='w-6' />
<Text size='sm'>{chain.name}</Text>
</li>
))}
</ul>
{process.env.NEXT_PUBLIC_NETWORK === NETWORK.MAINNET && (
<>
/>
<Overlay
show={showMenu}
setShow={setShowMenu}
className='right-0 w-[200px] mt-2 overflow-hidden'
>
{availableChains.map((chain, index) => (
<React.Fragment key={chain.chainId}>
<div
className={classNames(
'flex w-full items-center bg-white/5 px-4 py-3',
'border border-transparent border-y-white/10',
'flex items-center gap-2 px-4 py-3 border-b bg-white/10 border-white/20',
index > 0 && 'border-t',
)}
>
<Text size='lg' className='font-bold'>
V1 Outposts
</Text>
<ChainLogo chainID={chain.chainId} className='w-5' />
<Text>{chain.name}</Text>
</div>
<ul className='w-full px-4 py-3 list-none'>
{v1Outposts.map((outpost) => (
<li
className='flex items-center w-full gap-3 py-2 text-white group/chain opacity-60 hover:opacity-100'
role='button'
onClick={() => window.open(outpost.url, '_blank')}
key={outpost.name}
>
<ChainLogo chainID={outpost.chainId} className='w-6' />
<Text size='sm'>
{outpost.name} <ExternalLink className='w-4 ml-1 mb-0.5 inline' />
</Text>
</li>
))}
</ul>
</>
)}
{!!chains[chain.chainId] && (
<ChainOption
chainConfig={chains[chain.chainId]}
onSelect={() => selectChain(chains[chain.chainId])}
active={chainConfig.name === chain.name && !isV1}
/>
)}
<ChainOption
chainConfig={chains[chain.chainId]}
outpost={v1Outposts.find((outpost) => outpost.chainId === chain.chainId)}
active={chainConfig.name === chain.name && isV1}
/>
</React.Fragment>
))}
</Overlay>
</div>
)

View File

@ -16,7 +16,7 @@ import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
import { getGovernanceUrl } from 'utils/helpers'
export const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
{
pages: ['trade', 'trade-advanced'],
label: 'Trade',
@ -49,6 +49,7 @@ export default function DesktopHeader() {
const isOracleStale = useStore((s) => s.isOracleStale)
const isHLS = useStore((s) => s.isHLS)
const accountId = useAccountId()
const showAccountMenu = address && !isHLS
function handleCloseFocusMode() {
if (focusComponent && focusComponent.onClose) focusComponent.onClose()
@ -72,7 +73,7 @@ export default function DesktopHeader() {
focusComponent ? 'relative isolate' : 'border-b border-white/20',
)}
>
<DesktopNavigation />
<DesktopNavigation menuTree={menuTree} />
{focusComponent ? (
<div className='flex justify-between w-full'>
@ -92,7 +93,7 @@ export default function DesktopHeader() {
<div className='flex gap-4'>
{showStaleOracle && <OracleResyncButton />}
{accountId && <RewardsCenter />}
{address && !isHLS && <AccountMenu />}
{showAccountMenu && <AccountMenu />}
<Wallet />
<ChainSelect />
<Settings />

View File

@ -0,0 +1,81 @@
import classNames from 'classnames'
import { useMemo } from 'react'
import { isDesktop } from 'react-device-detect'
import Wallet from 'components/Wallet'
import EscButton from 'components/common/Button/EscButton'
import Settings from 'components/common/Settings'
import ChainSelect from 'components/header/ChainSelect'
import OracleResyncButton from 'components/header/OracleResyncButton'
import RewardsCenter from 'components/header/RewardsCenter'
import DesktopNavigation from 'components/header/navigation/DesktopNavigation'
import useAccountId from 'hooks/useAccountId'
import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
import { getGovernanceUrl } from 'utils/helpers'
const menuTree = (walletId: WalletID, chainConfig: ChainConfig): MenuTreeEntry[] => [
{
pages: ['v1'],
label: 'Red Bank',
},
{ pages: ['governance'], label: 'Governance', externalUrl: getGovernanceUrl(walletId) },
]
export default function DesktopHeader() {
const address = useStore((s) => s.address)
const focusComponent = useStore((s) => s.focusComponent)
const isOracleStale = useStore((s) => s.isOracleStale)
const accountId = useAccountId()
function handleCloseFocusMode() {
if (focusComponent && focusComponent.onClose) focusComponent.onClose()
useStore.setState({ focusComponent: null })
}
const showStaleOracle = useMemo(() => isOracleStale && address, [isOracleStale, address])
if (!isDesktop) return null
return (
<header
className={classNames(
'fixed left-0 top-0 z-50 w-full',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:h-full before:w-full before:rounded-sm before:backdrop-blur-sticky',
)}
>
<div
className={classNames(
'flex items-center justify-between px-4 py-4',
focusComponent ? 'relative isolate' : 'border-b border-white/20',
)}
>
<DesktopNavigation menuTree={menuTree} />
{focusComponent ? (
<div className='flex justify-between w-full'>
<div className='flex h-5 w-13' />
{address && (
<div className='flex gap-4'>
<Wallet />
<ChainSelect />
</div>
)}
<div className='flex gap-4'>
{!address && <ChainSelect />}
<EscButton onClick={handleCloseFocusMode} />
</div>
</div>
) : (
<div className='flex gap-4'>
{showStaleOracle && <OracleResyncButton />}
{accountId && <RewardsCenter />}
<Wallet />
<ChainSelect />
<Settings />
</div>
)}
</div>
</header>
)
}

View File

@ -4,7 +4,6 @@ import { useMemo } from 'react'
import Button from 'components/common/Button'
import { ChevronDown, Logo } from 'components/common/Icons'
import { menuTree } from 'components/header/DesktopHeader'
import { NavLink } from 'components/header/navigation/NavLink'
import { NavMenu } from 'components/header/navigation/NavMenu'
import useChainConfig from 'hooks/useChainConfig'
@ -12,19 +11,24 @@ import useToggle from 'hooks/useToggle'
import useStore from 'store'
import { WalletID } from 'types/enums/wallet'
interface Props {
menuTree: (walletId: WalletID, chainConfig: ChainConfig) => MenuTreeEntry[]
}
export function getIsActive(pages: string[]) {
const segments = location.pathname.split('/')
return pages.some((page) => segments.includes(page))
}
export default function DesktopNavigation() {
export default function DesktopNavigation(props: Props) {
const { menuTree } = props
const [showMenu, setShowMenu] = useToggle()
const { recentWallet } = useShuttle()
const chainConfig = useChainConfig()
const walletId = (recentWallet?.providerId as WalletID) ?? WalletID.Keplr
const focusComponent = useStore((s) => s.focusComponent)
const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig])
const menu = useMemo(() => menuTree(walletId, chainConfig), [walletId, chainConfig, menuTree])
return (
<div

View File

@ -12,6 +12,7 @@ import PerpsPage from 'pages/PerpsPage'
import PortfolioAccountPage from 'pages/PortfolioAccountPage'
import PortfolioPage from 'pages/PortfolioPage'
import TradePage from 'pages/TradePage'
import V1Page from 'pages/V1Page'
import Layout from 'pages/_layout'
export default function Routes() {
@ -32,6 +33,7 @@ export default function Routes() {
<Route path='/lend' element={<LendPage />} />
<Route path='/borrow' element={<BorrowPage />} />
<Route path='/portfolio' element={<PortfolioPage />} />
<Route path='/v1' element={<V1Page />} />
<Route path='/mobile' element={<MobilePage />} />
{chainConfig.hls && <Route path='/hls-staking' element={<HLSStakingPage />} />}
{chainConfig.hls && <Route path='/hls-farm' element={<HLSFarmPage />} />}
@ -47,6 +49,7 @@ export default function Routes() {
<Route path='portfolio' element={<PortfolioPage />} />
{chainConfig.hls && <Route path='hls-staking' element={<HLSStakingPage />} />}
{chainConfig.hls && <Route path='hls-farm' element={<HLSFarmPage />} />}
<Route path='v1' element={<V1Page />} />
<Route path='portfolio/:accountId'>
<Route path='' element={<PortfolioAccountPage />} />
</Route>

View File

@ -17,6 +17,7 @@ import { DEFAULT_PORTFOLIO_STATS } from 'utils/constants'
interface Props {
accountId: string
v1?: boolean
}
function Content(props: Props) {
@ -74,21 +75,21 @@ function Content(props: Props) {
title: (
<FormattedNumber
className='text-xl'
amount={leverage.toNumber()}
amount={isNaN(leverage.toNumber()) ? 1 : leverage.toNumber()}
options={{ suffix: 'x' }}
/>
),
sub: DEFAULT_PORTFOLIO_STATS[4].sub,
sub: props.v1 ? 'Total Leverage' : DEFAULT_PORTFOLIO_STATS[4].sub,
},
]
}, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs])
}, [account, assets, borrowAssets, hlsStrategies, lendingAssets, prices, vaultAprs, props.v1])
return (
<Skeleton
stats={stats}
health={health}
healthFactor={healthFactor}
title={`Credit Account ${props.accountId}`}
title={props.v1 ? 'V1 Portfolio' : `Credit Account ${props.accountId}`}
accountId={props.accountId}
/>
)

View File

@ -89,7 +89,7 @@ export default function PortfolioSummary() {
title: (
<FormattedNumber
className='text-xl'
amount={leverage.toNumber()}
amount={isNaN(leverage.toNumber()) ? 1 : leverage.toNumber()}
options={{ suffix: 'x' }}
/>
),

View File

@ -0,0 +1,47 @@
import BorrowingsTable from 'components/borrow/Table/ActiveBorrowingsTable'
import useV1BorrowingsTableData from 'components/v1/Table/useV1BorrowingsTableData'
import { BN_ZERO } from 'constants/math'
import useBorrowEnabledAssets from 'hooks/assets/useBorrowEnabledAssets'
export default function Borrowings() {
const { debtAssets } = useV1BorrowingsTableData()
if (!debtAssets?.length) {
return <Fallback />
}
return (
<>
<BorrowingsTable data={debtAssets} isLoading={false} v1 />
</>
)
}
function Fallback() {
const assets = useBorrowEnabledAssets()
const data: BorrowMarketTableData[] = assets.map((asset) => ({
asset,
apy: {
borrow: 0,
deposit: 0,
},
ltv: {
max: 0,
liq: 0,
},
liquidity: BN_ZERO,
marketLiquidityRate: 0,
cap: {
denom: asset.denom,
max: BN_ZERO,
used: BN_ZERO,
},
debt: BN_ZERO,
borrowEnabled: true,
depositEnabled: true,
deposits: BN_ZERO,
accountDebt: BN_ZERO,
}))
return <BorrowingsTable data={data} isLoading v1 />
}

View File

@ -0,0 +1,46 @@
import DepositsTable from 'components/earn/lend/Table/DepositedLendsTable'
import useV1DepositsTableData from 'components/v1/Table/useV1DepositsTableData'
import { BN_ZERO } from 'constants/math'
import useMarketEnabledAssets from 'hooks/assets/useMarketEnabledAssets'
export default function Deposits() {
const { depositAssets } = useV1DepositsTableData()
if (!depositAssets?.length) {
return <Fallback />
}
return (
<>
<DepositsTable data={depositAssets} isLoading={false} v1 />
</>
)
}
function Fallback() {
const assets = useMarketEnabledAssets()
const data: LendingMarketTableData[] = assets.map((asset) => ({
asset,
borrowEnabled: true,
depositEnabled: true,
debt: BN_ZERO,
deposits: BN_ZERO,
liquidity: BN_ZERO,
cap: {
max: BN_ZERO,
used: BN_ZERO,
denom: asset.denom,
},
apy: {
borrow: 0,
deposit: 0,
},
ltv: {
max: 0,
liq: 0,
},
}))
return <DepositsTable data={data} isLoading v1 />
}

View File

@ -0,0 +1,20 @@
import BorrowButton from 'components/v1/Table/borrowings/Columns/BorrowButton'
import Manage from 'components/v1/Table/borrowings/Columns/Manage'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: '',
}
interface Props {
data: BorrowMarketTableData
}
export default function Action(props: Props) {
const hasDebt = !props.data.accountDebtAmount?.isZero() ?? false
if (hasDebt) return <Manage data={props.data} />
return <BorrowButton data={props.data} />
}

View File

@ -0,0 +1,51 @@
import ActionButton from 'components/common/Button/ActionButton'
import { Plus } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useAccount from 'hooks/accounts/useAccount'
import useStore from 'store'
interface Props {
data: BorrowMarketTableData
}
export default function BorrowButton(props: Props) {
const address = useStore((s) => s.address)
const { data: account } = useAccount(address)
const hasCollateral = account?.lends?.length ?? 0 > 0
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={!hasCollateral}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have assets deposited in the Red Bank. Please deposit assets before you borrow.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<Plus />}
disabled={!hasCollateral}
color='tertiary'
onClick={(e) => {
useStore.setState({
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
})
e.stopPropagation()
}}
text='Borrow'
short
/>
</ConditionalWrapper>
</div>
)
}

View File

@ -0,0 +1,47 @@
import { useMemo } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { HandCoins, Plus } from 'components/common/Icons'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: BorrowMarketTableData
}
export default function Manage(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <Plus />,
text: 'Borrow more',
onClick: () =>
useStore.setState({
v1BorrowAndRepayModal: { type: 'borrow', data: props.data },
}),
},
{
icon: <HandCoins />,
text: 'Repay',
onClick: () =>
useStore.setState({
v1BorrowAndRepayModal: { type: 'repay', data: props.data },
}),
disabled: !hasBalance,
disabledTooltip: `You dont have any ${props.data.asset.symbol} in your Wallet.`,
},
],
[hasBalance, props.data],
)
return (
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}

View File

@ -0,0 +1,19 @@
import DepositButton from 'components/v1/Table/deposits/Columns/DepositButton'
import Manage from 'components/v1/Table/deposits/Columns/Manage'
export const MANAGE_META = {
accessorKey: 'manage',
enableSorting: false,
header: '',
}
interface Props {
data: LendingMarketTableData
}
export default function Action(props: Props) {
const hasDeposits = !props.data.accountLentAmount?.isZero() ?? false
if (hasDeposits) return <Manage data={props.data} />
return <DepositButton data={props.data} />
}

View File

@ -0,0 +1,51 @@
import ActionButton from 'components/common/Button/ActionButton'
import { ArrowUpLine } from 'components/common/Icons'
import Text from 'components/common/Text'
import { Tooltip } from 'components/common/Tooltip'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: LendingMarketTableData
}
export default function DepositButton(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
return (
<div className='flex justify-end'>
<ConditionalWrapper
condition={!hasBalance}
wrapper={(children) => (
<Tooltip
type='warning'
content={
<Text size='sm'>{`You dont have any ${props.data.asset.symbol} in your Wallet.`}</Text>
}
contentClassName='max-w-[200px]'
className='ml-auto'
>
{children}
</Tooltip>
)}
>
<ActionButton
leftIcon={<ArrowUpLine />}
disabled={!hasBalance}
color='tertiary'
onClick={(e) => {
useStore.setState({
v1DepositAndWithdrawModal: { type: 'deposit', data: props.data },
})
e.stopPropagation()
}}
text='Deposit'
short
/>
</ConditionalWrapper>
</div>
)
}

View File

@ -0,0 +1,48 @@
import { useMemo } from 'react'
import DropDownButton from 'components/common/Button/DropDownButton'
import { ArrowDownLine, ArrowUpLine } from 'components/common/Icons'
import useLendAndReclaimModal from 'hooks/useLendAndReclaimModal'
import useWalletBalances from 'hooks/useWalletBalances'
import useStore from 'store'
import { byDenom } from 'utils/array'
interface Props {
data: LendingMarketTableData
}
export default function Manage(props: Props) {
const address = useStore((s) => s.address)
const { data: balances } = useWalletBalances(address)
const hasBalance = !!balances.find(byDenom(props.data.asset.denom))
const ITEMS: DropDownItem[] = useMemo(
() => [
{
icon: <ArrowUpLine />,
text: 'Deposit more',
onClick: () =>
useStore.setState({
v1DepositAndWithdrawModal: { type: 'deposit', data: props.data },
}),
disabled: !hasBalance,
disabledTooltip: `You dont have any ${props.data.asset.symbol} in your Wallet.`,
},
{
icon: <ArrowDownLine />,
text: 'Withdraw',
onClick: () =>
useStore.setState({
v1DepositAndWithdrawModal: { type: 'withdraw', data: props.data },
}),
},
],
[hasBalance, props.data],
)
return (
<div className='z-10 flex justify-end'>
<DropDownButton items={ITEMS} text='Manage' color='tertiary' />
</div>
)
}

View File

@ -0,0 +1,38 @@
import { useMemo } from 'react'
import { BN_ZERO } from 'constants/math'
import useAccount from 'hooks/accounts/useAccount'
import useMarkets from 'hooks/markets/useMarkets'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
import useStore from 'store'
export default function useV1BorrowingsTableData() {
const address = useStore((s) => s.address)
const markets = useMarkets()
const { data: v1Positions } = useAccount(address)
const { convertAmount } = useDisplayCurrencyPrice()
return useMemo((): {
debtAssets: BorrowMarketTableData[]
} => {
const userDebts = v1Positions?.debts ?? []
const debtAssets: BorrowMarketTableData[] = []
markets
.filter((market) => market.borrowEnabled)
.forEach((market) => {
const amount =
userDebts.find((debt) => debt.denom === market.asset.denom)?.amount ?? BN_ZERO
const value = amount ? convertAmount(market.asset, amount) : undefined
const borrowMarketAsset: BorrowMarketTableData = {
...market,
accountDebtAmount: amount,
accountDebtValue: value,
}
debtAssets.push(borrowMarketAsset)
})
return { debtAssets }
}, [v1Positions, markets, convertAmount])
}

View File

@ -0,0 +1,39 @@
import { useMemo } from 'react'
import { BN_ZERO } from 'constants/math'
import useAccount from 'hooks/accounts/useAccount'
import useMarkets from 'hooks/markets/useMarkets'
import useDisplayCurrencyPrice from 'hooks/useDisplayCurrencyPrice'
import useStore from 'store'
import { byDenom } from 'utils/array'
export default function useV1DepositsTableData(): {
depositAssets: LendingMarketTableData[]
} {
const address = useStore((s) => s.address)
const markets = useMarkets()
const { data: v1Positions } = useAccount(address)
const { convertAmount } = useDisplayCurrencyPrice()
return useMemo(() => {
const depositAssets: LendingMarketTableData[] = []
const userCollateral = v1Positions?.lends ?? []
markets.forEach((market) => {
const amount = userCollateral.find(byDenom(market.asset.denom))?.amount ?? BN_ZERO
const value = amount ? convertAmount(market.asset, amount) : undefined
const lendingMarketAsset: LendingMarketTableData = {
...market,
accountLentValue: value,
accountLentAmount: amount,
}
depositAssets.push(lendingMarketAsset)
})
return {
depositAssets,
}
}, [markets, v1Positions, convertAmount])
}

View File

@ -0,0 +1,24 @@
import WalletConnectButton from 'components/Wallet/WalletConnectButton'
import Intro from 'components/common/Intro'
import useStore from 'store'
export default function V1Intro() {
const address = useStore((state) => state.address)
return (
<Intro
text={
<>
<span className='text-white'>Welcome to the Red Bank!</span>
<br />
This is the first version (v1) of the Red Bank. It provides simple lending and borrowing,
without the use of Credit Accounts.
<br />
Deposited funds can&lsquo;t be used on v2 as collateral.
</>
}
bg='v1'
>
{!address && <WalletConnectButton className='mt-4' />}
</Intro>
)
}

View File

@ -0,0 +1,16 @@
const stkATOM: AssetMetaData = {
symbol: 'stkATOM',
name: 'Persistence Staked Atom',
id: 'stkATOM',
color: '#c73238',
logo: '/images/tokens/stkatom.svg',
decimals: 6,
hasOraclePrice: true,
isEnabled: true,
isMarket: true,
isDisplayCurrency: true,
isAutoLendEnabled: false,
isStaking: true,
}
export default stkATOM

View File

@ -0,0 +1,18 @@
const wstETH: AssetMetaData = {
symbol: 'wstETH',
id: 'wstETH',
name: 'Lido Wrapped Staked Ethereum',
color: '#00a3ff',
logo: '/images/tokens/wsteth.svg',
decimals: 18,
hasOraclePrice: true,
isEnabled: true,
isMarket: true,
isDisplayCurrency: true,
isAutoLendEnabled: true,
isBorrowEnabled: true,
pythPriceFeedId: '0x6df640f3b8963d8f8358f791f352b8364513f6ab1cca5ed3f1f7b5448980e784',
pythFeedName: 'WSTETHUSD',
}
export default wstETH

View File

@ -0,0 +1,86 @@
import { Bech32Address } from '@keplr-wallet/cosmos'
import ATOM from 'configs/assets/ATOM'
import DYDX from 'configs/assets/DYDX'
import NTRN from 'configs/assets/NTRN'
import USDCaxl from 'configs/assets/USDC.axl'
import USDollar from 'configs/assets/USDollar'
import WETHaxl from 'configs/assets/WETH.axl'
import stATOM from 'configs/assets/stATOM'
import stkATOM from 'configs/assets/stkATOM'
import wstETH from 'configs/assets/wstETH'
import { NETWORK } from 'types/enums/network'
import { ChainInfoID } from 'types/enums/wallet'
const Neutron1: ChainConfig = {
assets: [
{ ...NTRN, denom: 'untrn' },
{ ...USDCaxl, denom: 'ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349' },
{
...ATOM,
denom: 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9',
},
{
...stATOM,
denom: 'ibc/B7864B03E1B9FD4F049243E92ABD691586F682137037A9F3FCA5222815620B3C',
},
{
...stkATOM,
denom: 'ibc/3649CE0C8A2C79048D8C6F31FF18FA69C9BC7EB193512E0BD03B733011290445',
},
{ ...WETHaxl, denom: 'ibc/A585C2D15DCD3B010849B453A2CFCB5E213208A5AB665691792684C26274304D' },
{
...wstETH,
denom: 'factory/neutron1ug740qrkquxzrk2hh29qrlx3sktkfml3je7juusc2te7xmvsscns0n2wry/wstETH',
},
{ ...DYDX, denom: 'ibc/2CB87BCE0937B1D1DFCEE79BE4501AAF3C265E923509AEAC410AD85D27F35130' },
USDollar,
],
id: ChainInfoID.Neutron1,
name: 'Neutron',
contracts: {
redBank: 'neutron1n97wnm7q6d2hrcna3rqlnyqw2we6k0l8uqvmyqq6gsml92epdu7quugyph',
incentives: 'neutron1aszpdh35zsaz0yj80mz7f5dtl9zq5jfl8hgm094y0j0vsychfekqxhzd39',
oracle: 'neutron1dwp6m7pdrz6rnhdyrx5ha0acsduydqcpzkylvfgspsz60pj2agxqaqrr7g',
swapper: 'neutron1udr9fc3kd743dezrj38v2ac74pxxr6qsx4xt4nfpcfczgw52rvyqyjp5au',
params: 'neutron16kqg3hr2qc36gz2wqvdzsctatkmzd3ss5gc07tnj6u3n5ajw89asrx8hfp',
creditManager: 'neutron1kj50g96c86nu7jmy5y7uy5cyjanntgru0eekmwz2qcmyyvx6383s8dgvm6',
accountNft: 'neutron17wvpxdc3k37054ume0ga4r0r6ra2rpfe622m0ecgd9s7xd5s0qusspc4ct',
perps: 'neutron14v9g7regs90qvful7djcajsvrfep5pg9qau7qm6wya6c2lzcpnms692dlt',
pyth: 'neutron1m2emc93m9gpwgsrsf2vylv9xvgqh654630v7dfrhrkmr5slly53spg85wv',
},
endpoints: {
routes: 'https://app.astroport.fi/api/routes',
rpc: process.env.NEXT_PUBLIC_NEUTRON_RPC ?? 'https://rpc-kralum.neutron-1.neutron.org',
rest: process.env.NEXT_PUBLIC_NEUTRON_REST ?? 'https://rest-kralum.neutron-1.neutron.org',
swap: 'https://neutron.astroport.fi/swap',
pools: '', //TODO: ⛓️ Implement this
explorer: 'https://mintscan.io/neutron',
aprs: {
vaults: 'https://api.marsprotocol.io/v1/vaults/neutron',
stride: 'https://edge.stride.zone/api/stake-stats',
},
},
network: NETWORK.MAINNET,
vaults: [],
explorerName: 'Mintscan',
bech32Config: Bech32Address.defaultBech32Config('neutron'),
defaultCurrency: {
coinDenom: 'NTRN',
coinMinimalDenom: 'untrn',
coinDecimals: 6,
coinGeckoId: 'neutron',
gasPriceStep: {
low: 0,
average: 0.025,
high: 0.04,
},
},
features: ['ibc-transfer', 'ibc-go'],
gasPrice: '0.025untrn',
hls: false,
perps: false,
farm: false,
}
export default Neutron1

View File

@ -13,7 +13,7 @@ import USDCaxl from 'configs/assets/USDC.axl'
import USDT from 'configs/assets/USDT'
import USDollar from 'configs/assets/USDollar'
import WBTCaxl from 'configs/assets/WBTC.axl'
import WETHaxl from 'configs/assets/WETH.xal'
import WETHaxl from 'configs/assets/WETH.axl'
import OSMO_ATOM from 'configs/assets/lp/OSMO-ATOM'
import OSMO_USDC from 'configs/assets/lp/OSMO_USDC'
import OSMO_WBTC from 'configs/assets/lp/OSMO_WBTC'

View File

@ -47,6 +47,11 @@ const PAGE_METADATA = {
'Stake MARS token to ascend to the Martian Council and help govern key changes to the protocol.',
keywords: 'martian council, mars governance, cosmos governance, mars voting, mars staking',
},
v1: {
title: 'Mars Protocol V1',
description: "Lend, borrow and earn on the galaxy's most powerful credit protocol.",
keywords: 'martian council, mars governance, cosmos governance, mars voting, mars staking',
},
}
export default PAGE_METADATA

View File

@ -15,7 +15,7 @@ export const WALLETS: WalletInfos = {
walletConnect: 'Cosmostation WalletConnect',
imageURL: '/images/wallets/cosmostation.png',
mobileImageURL: '/images/wallets/cosmostation-wc.png',
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet],
supportedChains: [ChainInfoID.Osmosis1, ChainInfoID.OsmosisDevnet, ChainInfoID.Pion1],
},
[WalletID.Keplr]: {
name: 'Keplr Wallet',

View File

@ -1,14 +1,22 @@
import useSWR from 'swr'
import getAccount from 'api/accounts/getAccount'
import getV1Positions from 'api/v1/getV1Positions'
import useChainConfig from 'hooks/useChainConfig'
import useStore from 'store'
export default function useAccount(accountId?: string, suspense?: boolean) {
const chainConfig = useChainConfig()
const address = useStore((s) => s.address)
const isV1 = accountId === address
const cacheKey = isV1
? `chains/${chainConfig.id}/v1/user/${accountId}`
: `chains/${chainConfig.id}/accounts/${accountId}`
return useSWR(
accountId && `chains/${chainConfig.id}/accounts/${accountId}`,
() => getAccount(chainConfig, accountId),
accountId && cacheKey,
() => (isV1 ? getV1Positions(chainConfig, accountId) : getAccount(chainConfig, accountId)),
{
suspense: suspense,
revalidateOnFocus: false,

View File

@ -11,6 +11,7 @@ import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.cl
import { MarsPerpsQueryClient } from 'types/generated/mars-perps/MarsPerps.client'
import { MarsRedBankQueryClient } from 'types/generated/mars-red-bank/MarsRedBank.client'
import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client'
import { getUrl } from 'utils/url'
export default function useClients() {
const chainConfig = useChainConfig()
@ -18,8 +19,7 @@ export default function useClients() {
const swr = useSWR(
`chains/${chainConfig.id}/clients`,
async () => {
const client = await CosmWasmClient.connect(chainConfig.endpoints.rpc)
const client = await CosmWasmClient.connect(getUrl(chainConfig.endpoints.rpc))
return {
creditManager: new MarsCreditManagerQueryClient(
client,

View File

@ -1,11 +1,9 @@
import Borrowings from 'components/borrow/Borrowings'
import BorrowIntro from 'components/borrow/BorrowIntro'
import MigrationBanner from 'components/common/MigrationBanner'
export default function BorrowPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<BorrowIntro />
<Borrowings />
</div>

View File

@ -1,13 +1,11 @@
import Tab from 'components/earn/Tab'
import FarmIntro from 'components/earn/farm/FarmIntro'
import Vaults from 'components/earn/farm/Vaults'
import Tab from 'components/earn/Tab'
import MigrationBanner from 'components/common/MigrationBanner'
import { EARN_TABS } from 'constants/pages'
export default function FarmPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<Tab tabs={EARN_TABS} activeTabIdx={1} />
<FarmIntro />
<Vaults />

View File

@ -1,13 +1,11 @@
import Tab from 'components/earn/Tab'
import AvailableHLSVaults from 'components/hls/Farm/AvailableHLSVaults'
import HlsFarmIntro from 'components/hls/Farm/HLSFarmIntro'
import MigrationBanner from 'components/common/MigrationBanner'
import { HLS_TABS } from 'constants/pages'
export default function HLSFarmPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<Tab tabs={HLS_TABS} activeTabIdx={1} />
<HlsFarmIntro />
<AvailableHLSVaults />

View File

@ -2,13 +2,11 @@ import Tab from 'components/earn/Tab'
import ActiveStakingAccounts from 'components/hls/Staking/ActiveStakingAccounts'
import AvailableHlsStakingAssets from 'components/hls/Staking/AvailableHLSStakingAssets'
import HLSStakingIntro from 'components/hls/Staking/HLSStakingIntro'
import MigrationBanner from 'components/common/MigrationBanner'
import { HLS_TABS } from 'constants/pages'
export default function HLSStakingPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<Tab tabs={HLS_TABS} activeTabIdx={0} />
<HLSStakingIntro />
<AvailableHlsStakingAssets />

View File

@ -1,4 +1,3 @@
import MigrationBanner from 'components/common/MigrationBanner'
import Tab from 'components/earn/Tab'
import LendIntro from 'components/earn/lend/LendIntro'
import Lends from 'components/earn/lend/Lends'
@ -10,7 +9,6 @@ export default function LendPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
{chainConfig.farm && <Tab tabs={EARN_TABS} activeTabIdx={0} />}
<LendIntro />
<Lends />

View File

@ -1,6 +1,5 @@
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
import MigrationBanner from 'components/common/MigrationBanner'
import ShareBar from 'components/common/ShareBar'
import Balances from 'components/portfolio/Account/Balances'
import BreadCrumbs from 'components/portfolio/Account/BreadCrumbs'
@ -25,7 +24,6 @@ export default function PortfolioAccountPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<BreadCrumbs accountId={accountId} />
<Summary accountId={accountId} />
<Balances accountId={accountId} />

View File

@ -1,13 +1,11 @@
import MigrationBanner from 'components/common/MigrationBanner'
import ShareBar from 'components/common/ShareBar'
import AccountOverview from 'components/portfolio/Overview'
import PortfolioSummary from 'components/portfolio/Overview/Summary'
import PortfolioIntro from 'components/portfolio/PortfolioIntro'
import ShareBar from 'components/common/ShareBar'
export default function PortfolioPage() {
return (
<div className='flex flex-wrap w-full gap-6'>
<MigrationBanner />
<PortfolioIntro />
<PortfolioSummary />
<AccountOverview />

View File

@ -1,7 +1,6 @@
import { useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import MigrationBanner from 'components/common/MigrationBanner'
import AccountDetailsCard from 'components/trade/AccountDetailsCard'
import TradeChart from 'components/trade/TradeChart'
import TradeModule from 'components/trade/TradeModule'
@ -47,7 +46,6 @@ export default function TradePage() {
)
return (
<div className='flex flex-col w-full h-full gap-4'>
<MigrationBanner />
<div className='grid w-full grid-cols-[auto_346px] gap-4'>
<TradeChart buyAsset={buyAsset} sellAsset={sellAsset} />
<TradeModule buyAsset={buyAsset} sellAsset={sellAsset} isAdvanced={isAdvanced} />

19
src/pages/V1Page.tsx Normal file
View File

@ -0,0 +1,19 @@
import Summary from 'components/portfolio/Account/Summary'
import Borrowings from 'components/v1/Borrowings'
import Deposits from 'components/v1/Deposits'
import V1Intro from 'components/v1/V1Intro'
import useStore from 'store'
export default function V1Page() {
const address = useStore((s) => s.address)
return (
<div className='flex flex-wrap w-full gap-6'>
<V1Intro />
{address && <Summary accountId={address} v1 />}
<div className='grid w-full grid-cols-1 gap-6 lg:grid-cols-2'>
<Deposits />
<Borrowings />
</div>
</div>
)
}

View File

@ -4,13 +4,14 @@ import { isMobile } from 'react-device-detect'
import { useLocation } from 'react-router-dom'
import { SWRConfig } from 'swr'
import ModalsContainer from 'components/Modals/ModalsContainer'
import AccountDetails from 'components/account/AccountDetails'
import Background from 'components/common/Background'
import Footer from 'components/common/Footer'
import PageMetadata from 'components/common/PageMetadata'
import Toaster from 'components/common/Toaster'
import DesktopHeader from 'components/header/DesktopHeader'
import ModalsContainer from 'components/Modals/ModalsContainer'
import V1DesktopHeader from 'components/header/V1DesktopHeader'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { LocalStorageKeys } from 'constants/localStorageKeys'
import useLocalStorage from 'hooks/localStorage/useLocalStorage'
@ -25,6 +26,8 @@ interface Props {
}
function PageContainer(props: Props) {
const isV1 = useStore((s) => s.isV1)
if (isMobile) return props.children
if (!props.focusComponent)
@ -32,7 +35,8 @@ function PageContainer(props: Props) {
<div
className={classNames(
'mx-auto flex items-start w-full',
!props.fullWidth && 'max-w-content',
!props.fullWidth && !isV1 && 'max-w-content',
isV1 && 'max-w-v1',
)}
>
{props.children}
@ -50,6 +54,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
const location = useLocation()
const focusComponent = useStore((s) => s.focusComponent)
const address = useStore((s) => s.address)
const isV1 = useStore((s) => s.isV1)
const [reduceMotion] = useLocalStorage<boolean>(
LocalStorageKeys.REDUCE_MOTION,
@ -67,7 +72,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<SWRConfig value={{ use: [debugSWR] }}>
<PageMetadata />
<Background />
<DesktopHeader />
{isV1 ? <V1DesktopHeader /> : <DesktopHeader />}
<main
className={classNames(
'lg:min-h-[calc(100dvh-81px)]',

View File

@ -16,6 +16,7 @@ import {
ExecuteMsg as CreditManagerExecuteMsg,
ExecuteMsg,
} from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { ExecuteMsg as RedBankExecuteMsg } from 'types/generated/mars-red-bank/MarsRedBank.types'
import { AccountKind } from 'types/generated/mars-rover-health-types/MarsRoverHealthTypes.types'
import { byDenom, bySymbol } from 'utils/array'
import { generateErrorMessage, getSingleValueFromBroadcastResult } from 'utils/broadcast'
@ -30,7 +31,7 @@ import { getVaultDepositCoinsFromActions } from 'utils/vaults'
function generateExecutionMessage(
sender: string | undefined = '',
contract: string,
msg: CreditManagerExecuteMsg | AccountNftExecuteMsg | PythUpdateExecuteMsg,
msg: CreditManagerExecuteMsg | AccountNftExecuteMsg | RedBankExecuteMsg | PythUpdateExecuteMsg,
funds: Coin[],
) {
return new MsgExecuteContract({
@ -1065,5 +1066,69 @@ export default function createBroadcastSlice(
{ denom: get().chainConfig.assets[0].denom, amount: String(pythAssets.length) },
])
},
v1Action: async (type: V1ActionType, coin: BNCoin) => {
let msg: RedBankExecuteMsg
let toastOptions: ToastObjectOptions = {
action: type,
accountId: get().address,
changes: {},
}
let funds: Coin[] = []
switch (type) {
case 'withdraw':
msg = {
withdraw: {
amount: coin.amount.toString(),
denom: coin.denom,
},
}
toastOptions = {
...toastOptions,
changes: { deposits: [coin] },
target: 'wallet',
}
break
case 'repay':
msg = {
repay: {},
}
toastOptions.changes = { deposits: [coin] }
funds = [coin.toCoin()]
break
case 'borrow':
msg = {
borrow: {
amount: coin.amount.toString(),
denom: coin.denom,
},
}
toastOptions = {
...toastOptions,
changes: { debts: [coin] },
target: 'wallet',
}
break
default:
msg = {
deposit: {},
}
toastOptions.changes = { deposits: [coin] }
funds = [coin.toCoin()]
}
const redBankContract = get().chainConfig.contracts.redBank
const response = get().executeMsg({
messages: [generateExecutionMessage(get().address, redBankContract, msg, funds)],
})
get().setToast({
response,
options: toastOptions,
})
return response.then((response) => !!response.result)
},
}
}

View File

@ -19,5 +19,6 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
useAutoRepay: true,
isOracleStale: false,
isHLS: false,
isV1: false,
}
}

View File

@ -19,5 +19,7 @@ export default function createModalSlice(set: SetState<ModalSlice>, get: GetStat
vaultModal: null,
walletAssetsModal: null,
withdrawFromVaultsModal: null,
v1DepositAndWithdrawModal: null,
v1BorrowAndRepayModal: null,
}
}

View File

@ -17,7 +17,8 @@ interface Market {
}
interface BorrowMarketTableData extends Market {
accountDebt?: BigNumber
accountDebtAmount?: BigNumber
accountDebtValue?: BigNumber
}
interface LendingMarketTableData extends Market {

View File

@ -11,6 +11,7 @@ type Page =
| 'hls-staking'
| 'governance'
| 'execute'
| 'v1'
type OsmosisRouteResponse = {
amount_in: {

Some files were not shown because too many files have changed in this diff Show More