Compare commits

..

3 Commits

Author SHA1 Message Date
Bob van der Helm
7ebd35fbfd
update hc wasm files 2023-11-23 15:12:06 +01:00
Bob van der Helm
4c88cfd99f
add logging 2023-11-23 11:05:12 +01:00
Bob van der Helm
373b147997
enable hls 2023-11-23 10:39:00 +01:00
787 changed files with 14400 additions and 51970 deletions

View File

@ -1,4 +0,0 @@
node_modules
.next
.git
.github

View File

@ -1,16 +1,37 @@
## Mandatory Environment Variables
# DEVNET #
NEXT_PUBLIC_NETWORK=devnet
NEXT_PUBLIC_CHAIN_ID=devnet
NEXT_PUBLIC_RPC=https://rpc.devnet.osmosis.zone/
NEXT_PUBLIC_GQL=https://devnet-osmosis-gql.marsprotocol.io/graphql
NEXT_PUBLIC_REST=https://lcd.devnet.osmosis.zone/
# MAINNET #
NEXT_PUBLIC_NETWORK=mainnet
NEXT_PUBLIC_WALLET_CONNECT_ID=0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x
NEXT_PUBLIC_CHAIN_ID=osmosis-1
NEXT_PUBLIC_RPC=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_GQL=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-hive-front/graphql
NEXT_PUBLIC_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-lcd-front/
### Optional Environment Variables
NEXT_PUBLIC_OSMOSIS_RPC=https://rpc-osmosis.blockapsis.com
NEXT_PUBLIC_OSMOSIS_REST=https://lcd-osmosis.blockapsis.com
NEXT_PUBLIC_OSMOSIS_TEST_RPC=https://rpc.devnet.osmosis.zone
NEXT_PUBLIC_OSMOSIS_TEST_REST=https://lcd.devnet.osmosis.zone
NEXT_PUBLIC_NEUTRON_TEST_RPC=https://rpc-palvus.pion-1.ntrn.tech
NEXT_PUBLIC_NEUTRON_TEST_REST=https://rest-palvus.pion-1.ntrn.tech
### Enable the following environment variables if you have access to the charting library
CHARTING_LIBRARY_USERNAME=git_username
CHARTING_LIBRARY_ACCESS_TOKEN=access_token
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library/
# COMMON #
NEXT_PUBLIC_SWAP=https://app.osmosis.zone
NEXT_PUBLIC_VAULT_APR=https://api.marsprotocol.io/v1/vaults/osmosis
NEXT_PUBLIC_ACCOUNT_NFT=osmo1450hrg6dv2l58c0rvdwx8ec2a0r6dd50hn4frk370tpvqjhy8khqw7sw09
NEXT_PUBLIC_ORACLE=osmo1mhznfr60vjdp2gejhyv2gax9nvyyzhd3z0qcwseyetkfustjauzqycsy2g
NEXT_PUBLIC_RED_BANK=osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg
NEXT_PUBLIC_CREDIT_MANAGER=osmo1f2m24wktq0sw3c0lexlg7fv4kngwyttvzws3a3r3al9ld2s2pvds87jqvf
NEXT_PUBLIC_INCENTIVES=osmo1nkahswfr8shg8rlxqwup0vgahp0dk4x8w6tkv3rra8rratnut36sk22vrm
NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
NEXT_PUBLIC_ZAPPER=osmo17qwvc70pzc9mudr8t02t3pl74hhqsgwnskl734p4hug3s8mkerdqzduf7c
NEXT_PUBLIC_PARAMS=osmo1nlmdxt9ctql2jr47qd4fpgzg84cjswxyw6q99u4y4u4q6c2f5ksq7ysent
NEXT_PUBLIC_PYTH_ENDPOINT=https://hermes.pyth.network/api
NEXT_PUBLIC_MAINNET_REST=https://osmosis-node.marsprotocol.io/GGSFGSFGFG34/osmosis-rpc-front/
NEXT_PUBLIC_CANDLES_ENDPOINT_THE_GRAPH=https://osmosis-candles.marsprotocol.io/
NEXT_PUBLIC_CANDLES_ENDPOINT_PYTH=https://benchmarks.pyth.network
NEXT_PUBLIC_WALLET_CONNECT_ID=d93fdffb159bae5ec87d8fee4cdbb045
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library
CHARTING_LIBRARY_ACCESS_TOKEN=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
CHARTING_LIBRARY_USERNAME=mars-git-demo
NEXT_PUBLIC_STRIDE_APRS=https://edge.stride.zone/api/stake-stats

26
.github/workflows/code-coverage.yaml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Jest Coverage Diff
on: pull_request
jobs:
coverage-check:
if: github.actor != 'dependabot[bot]'
name: Code Coverage Diff Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
cache: yarn
- name: Install dependencies
run: yarn
- name: Test Coverage
id: testCoverage
uses: anuraag016/Jest-Coverage-Diff@V1.4
with:
fullCoverageDiff: false
runCommand: "yarn jest --coverage --collectCoverage=true --coverageDirectory='./' --coverageReporters='json-summary' --forceExit --detectOpenHandles"
delta: 0.1

View File

@ -1,25 +0,0 @@
name: Docker Image CI
on:
push:
branches:
- 'main'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: marsprotocol/interface:latest, marsprotocol/interface:v2-${{ github.run_number }}

19
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Unit Tests
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
cache: yarn
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test --coverage false

8
.gitignore vendored
View File

@ -19,10 +19,8 @@ coverage-summary.json
# misc
.DS_Store
*.pem
src/utils/charting_library/
src/utils/datafeeds/
/public/charting_library/
/public/datafeeds/
charting_library/
datafeeds/
# debug
npm-debug.log*
@ -44,6 +42,8 @@ next-env.d.ts
.env.local
.env.local.testnet
.env.local.mainnet
.env.production
.env
# IDE
.idea

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View File

@ -1,41 +1,59 @@
FROM node:20-alpine as builder
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN apk --update add patch
RUN patch next.config.js next-config.patch
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
ENV NEXT_PUBLIC_NETWORK=mainnet
ENV NEXT_PUBLIC_OSMOSIS_RPC=APP_NEXT_OSMOSIS_RPC
ENV NEXT_PUBLIC_OSMOSIS_REST=APP_NEXT_OSMOSIS_REST
ENV NEXT_PUBLIC_WALLET_CONNECT_ID=APP_NEXT_WALLET_CONNECT_ID
ENV NODE_ENV=production
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
FROM node:20-alpine as runner
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
COPY --from=builder /app/package.json .
COPY --from=builder /app/yarn.lock .
COPY --from=builder /app/next.config.js .
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY entrypoint.sh .
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN apk add --no-cache --upgrade bash
RUN ["chmod", "+x", "./entrypoint.sh"]
ENTRYPOINT ["./entrypoint.sh"]
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
# Labels
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.title="mars-fe"
LABEL org.opencontainers.image.description="Mars Protocol Outpost Frontend"
LABEL org.opencontainers.image.authors="info@mars.foundation"
LABEL org.opencontainers.image.source=https://github.com/mars-protocol/mars-v2-frontend
ENV PORT 3000
CMD ["node", "server.js"]

461
LICENSE
View File

@ -1,461 +0,0 @@
MARS PROTOCOL WEB APPLICATION LICENSE AGREEMENT
Version 1, 24 January 2023
This Mars Protocol Web Application License Agreement (this “Agreement”)is
a legally binding agreement with Delphi Labs Ltd., a British Virgin Islands
company limited by shares (“Licensor”) pertaining to all software and
technologies contained in this repository (whether in source code,
object code or other form).
YOU ARE NOT PERMITTED TO USE THIS SOFTWARE EXCEPT FOR PURPOSES OF FACILITATING
USE OF DEPLOYED INSTANCES OF THE PROTOCOL THAT ARE ENDORSED BY THE MARTIAN
COUNCIL, UPON THE TERMS AND CONDITIONS SET FORTH BELOW.
YOU ARE NOT PERMITTED TO USE THIS SOFTWARE IN CONNECTION WITH FORKS OF
THE PROTOCOL NOT ENDORSED BY THE MARTIAN COUNCIL, OR IN CONNECTION WITH
OTHER PROTOCOLS.
1. RESERVATION OF PROPRIETARY RIGHTS.
Except to the extent provided in Section 2:
a. all intellectual property (including all trade secrets, source
code, designs and protocols) relating to the Web Application has
been published or made available for informational purposes only
(e.g., to enable users of the Web Application to conduct their
own due diligence into the security and other risks thereof);
b. no license, right of reproduction or distribution or other right
with respect to the Web Application or any other intellectual
property is granted or implied; andc.all moral, intellectual
property and other rights relating to the Web Application and
other intellectual property are hereby reserved by Licensor
(and the other contributors to such intellectual property or
holders of such rights, as applicable).
2. LIMITED LICENSE.
Upon the terms and subject to the conditions set forth in this Agreement
(including the conditions set forth in Section 3), Licensor hereby grants
a non-transferable, personal, non-sub-licensable, global, royalty-free,
revocable license in Licensors intellectual property rights relating to
the Web Application:
a. to each Authorized Site Operator, to run the Web Application for
use by each Authorized User solely in connection with the Endorsed
Smart Contracts (and not for any of the purposes described in
ection 3);
b. to each Authorized User, to use the Web Application run by an
Authorized Site Operatorsolely in connection with the Endorsed
Smart Contracts (and not for any of the purposes described in
Section 3); and
The following capitalized terms have the definitions that are ascribed
to them below:
Defined Terms Relating to Relevant Persons
“Authorized Site Operator” means a person who makes the un-modified
Web Application available to persons in good faith on commercially
reasonable terms for purposes of facilitating their use of the
Endorsed Smart Contracts for their intended purposes and complies
with the conditions set forth in Section 3.
“Authorized User” means a person who uses the un-modified Web
Application in good faith for purposes of using the Endorsed Smart
Contracts for their intended purposes and complies with the conditions
set forth in Section 3.
“Web Application” means the software at
<https://github.com/mars-protocol/webapp>, as it may be updated from
time to time by Licensor.
Defined Terms Relating to Mars Hub & Martian Council
“$MARS” means the native token unit of Mars Hub the bonding, staking
or delegation of which determines which Mars Hub Core Nodes have the
ability to propose and validate new blocks on the Mars Hub blockchain.
“Mars Hub” means, at each time, the canonical blockchain and virtual
machine environment of the Mars Hub mainnet, as recognized by at
least a majority of the Mars Core Nodes then being operated in good
faith in the ordinary course of the network.
“Mars Hub Core” means the reference implementation of the Mars
blockchain hub protocol currently stored at
<https://github.com/mars-protocol/hub> or any successor thereto
expressly determined by the Martian Council to constitute the
reference implementation for Mars blockchain hub protocol.
“Mars Hub Core Nodes” means, at each time, the internet-connected
computers then running unaltered and correctly configured instances
of the most up-to-date production release of Mars Hub Core.
“Martian Council” means at each time, all persons holding $MARS that
is staked with or delegated or bonded to Mars Hub Core Nodes in the
active validator set for Mars Hub at such time and has the power to
vote such $MARS tokens on governance proposals in accordance with
the Mars Protocol.
Defined Terms Relating to Mars Protocol & Smart Contracts
“Endorsed Smart Contracts” means the Mainnet Smart Contracts and the
Testnet Smart Contracts.
“Mainnet Smart Contracts” means all runtime object code that satisfies
all of the following conditions precedent: (a) an instance of such code
is deployed to a production-grade, commercial-grade “mainnet”
blockchain-based network environment; (b) such code constitutes a part
of the Mars Protocol; and (c) such instance of such code has been
approved by the Martian Council to be governed by the Martian Council
through Mars Hub on such blockchain-based network environment.
“Mars Protocol” means the software code at <https://github.com/mars-protocol>
or any successor thereto expressly determined by the Martian Council to
constitute or form a part of the “Mars Protocol”.
“Testnet Smart Contracts” means all runtime object code that satisfies
all of the following conditions precedent: (a) an instance of such code
is deployed to a nonproduction-grade, non-commercial-grade “testnet”
blockchain-based network environment solely for testing purposes;
(b) such code constitutes a part of the Mars Protocol; and (c) such
deployment is in reasonable anticipation of, or follows, approval by
the Martian Council of Mainnet Smart Contracts for the corresponding
“mainnet” blockchain network environment.
3. CONDITIONS/PROHIBITED USES.
Notwithstanding Section 2, it is a condition precedent and condition
subsequent of the licenses granted hereunder that the Web Application must
not be used in connection with or in furtherance of:
a. developing, making available, running or operating the Web
Application for use by any person in connection with any smart
contracts other than the Endorsed Smart Contracts;
b. any device, plan, scheme or artifice to defraud, or otherwise
materially mislead, any person;
c. any fraud, deceit, material misrepresentation or other crime, tort
or illegal conduct againstany person or device, plan, scheme or
artifice for accomplishing the foregoing;
d. any violation, breach or failure to comply with any term or
condition of this Agreement(including any inaccuracy in a
epresentation of set forth in Section 4) or any other terms of
service, privacy policy, trading policy or other contract
governing the use of the Web Application or any Endorsed Smart
Contract;
e. any fork, copy, derivative or alternative instance of any Endorsed
Smart Contract;
f. any smart contract, platform or service that competes in any
material respect with any Endorsed Smart Contract;
g. any device, plan, scheme or artifice to obtain any unfair
competitive advantage over Licensor or other persons with an
economic or beneficial interest in the Mainnet Smart Contracts;
h. any device, plan, scheme or artifice to interfere with, damage,
impair or subvert the intended functioning of any Endorsed Smart
Contract,including in connection with any “sybil attack”,
“reentrancy attack”, “DoS attack,” “eclipse attack,” “consensus
attack,” “reentrancy attack,” “griefing attack”, “economic
incentive attack” or theft, conversion or
misappropriation of tokens or other similar action;
i. any “front-running,” “wash trading,” “pump and dump trading,”
“ramping,” “cornering” or other illegal, fraudulent, deceptive
or manipulative trading activities ;
j. any device, plan, scheme or artifice to unfairly or deceptively
influence the market price of any token; or
k. modifying or making derivative works based on the Web Application.
4. REPRESENTATIONS OF LICENSEES.
Each person making use of or relying on any license granted under Section 2
(each, a “Licensee”) hereby represents and warrants to Licensor that the
following statements and information are accurate and complete at all times
that such person makes use of or relies on the license.
a. Status. If Licensee is an individual, Licensee is of legal age in
the jurisdiction in which Licensee resides (and in any event is
older than thirteen years of age) and is of sound mind. If
Licensee is a business entity, Licensee is duly organized, validly
existing and in good standing under the laws of the jurisdiction
in which it is organized and has all requisite power and authority
for a business entity of its type to carry on its business as now
conducted.
b. Power and Authority. Licensee has all requisite capacity, power and
authority to accept this Agreement and to carry out and perform its
obligations under this Agreement. This Agreement constitutes a
legal, valid and binding obligation of Licensee, enforceable
against Licensee.
c. No Conflict; Compliance with Law. Licensee agreeing to this
Agreement and using the Web Application does not constitute, and
would not reasonably be expected to result in (with or without
notice, lapse of time, or both), a breach, default, contravention
or violation of any law applicable to Licensee, or contract or
agreement to which Licensee is a party or by which Licensee is
bound.
d. Absence of Sanctions. Licensee is not, (and, if Licensee is an
entity, Licensee is not owned or controlled by any other person
who is), and is not acting on behalf of any other person who is,
identified on any list of prohibited parties under any law or by
any nation or government, state or other political subdivision
thereof, any entity exercising legislative, judicial or
administrative functions of or pertaining to government such as
the lists maintained by the United Nations Security Council, the
United Kingdom, the British Virgin Islands, the United States
(including the U.S. Treasury Departments Specially Designated
Nationals list and Foreign Sanctions Evaders list), the European
Union (EU) or its member states, and the government of a Licensee
home country. Licensee is not, (and, if Licensee is an entity,
Licensee is not owned or controlled by any other person who is),
and is not acting on behalf of any other person who is, located,
ordinarily resident, organized, established, or domiciled in Cuba,
Iran, North Korea, Sudan, Syria, the Crimea region (including
Sevastopol) or any other country or jurisdiction against which the
United Nations, the United Kingdom, the British Virgin Islands or
the United States maintains economic sanctions or an arms embargo.
The tokens or other funds a Licensee use to participate in the Web
Application are not derived from, and do not otherwise represent
the proceeds of, any activities done in violation or contravention
of any law.
e. No Claim, Loan, Ownership Interest or Investment Purpose. Licensee
understands and agrees that the Licensees use of the Web
Application does not: (i) represent or constitute a loan or a
contribution of capital to, or other investment in Licensor or any
business or venture; (ii) provide Licensee with any ownership
interest, equity, security, or right to or interest in the assets,
rights, properties, revenues or profits of, or voting rights
whatsoever in, Licensor or any other business or venture; or (iii)
create or imply or entitle Licensee to the benefits of any
fiduciary or other agency relationship between Licensor or any of
its directors, officers, employees, agents or affiliates, on the
on hand, and Licensee, on the other hand. Licensee is not entering
into this Agreement or using the Web Application for the purpose
of making an investment with respect to Licensor or its securities,
but solely wishes to use the Web Application for their
intended purposes. Licensee understands and agrees that Licensor
will not accept or take custody over any tokens or money or other
assets of Licensee and has no responsibility or control over the
foregoing.
f. Non-Reliance. Licensee is knowledgeable, experienced and
sophisticated in using and evaluating blockchain and related
technologies and assets, including all technologies referenced
herein. Licensee has conducted its own thorough independent
investigation and analysis of the Web Application and the other
matters contemplated by this Agreement, and has not relied upon
any information, statement, omission, representation or warranty,
express or implied, written or oral, made by or on behalf of
Licensor in connection therewith.
5. RISKS, DISCLAIMERS AND LIMITATIONS OF LIABILITY.
THE WEB APPLICATION IS PROVIDED "AS IS" AND “AS-AVAILABLE,” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE
HEREBY DISCLAIMED. IN NO EVENT SHALL LICENSOR OR ANY OTHER CONTRIBUTOR TO
THE WEB APPLICATION BE LIABLE FOR ANY DAMAGES, INCLUDING ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR INTELLECTUAL PROPERTY
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION),
HOWEVER CAUSED OR CLAIMED (WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE)), EVEN IF SUCH DAMAGES WERE REASONABLY
FORESEEABLE OR THE COPYRIGHT HOLDERS AND CONTRIBUTORS WERE ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
6. GENERAL PROVISIONS
a. Governing Law. This Agreement shall be governed by and construed
under the internal laws of the British Virgin Islands, regardless
of the laws that might otherwise govern under applicable
principles of conflicts of laws.
b. Dispute Resolution. Licensee (i) hereby irrevocably and
unconditionally submits to the jurisdiction of the relevant courts
of the British Virgin Islands for the purpose of any dispute,
suit, action or other proceeding arising out of or based upon this
Agreement or the matters contemplated by this Agreement
(“Disputes”), (ii) agrees not to commence any suit, action or
other proceeding arising in connection with or based upon this
Agreement or the matters contemplated by this Agreement except
before the relevant courts of the British Virgin Islands, and
(iii) hereby waives, and agrees not to assert, by way of motion,
as a defense, or otherwise, in any such suit, action or
proceeding, any claim that it is not subject personally to the
jurisdiction of the above-named courts, that its property is
exempt or immune from attachment or execution, that the suit,
action or proceeding is brought in an inconvenient forum, that the
venue of the suit, action or proceeding is improper or that this
Agreement or the subject matter hereof or thereof may not be
enforced in or by such court.
Each party will bear its own costs in respect of any Disputes.
Notwithstanding the foregoing, at the Licensors sole option and
commencing within a reasonable period from the date of
notification to the other party of such Dispute, any Dispute may
be resolved by confidential, binding arbitration to be seated in
the British Virgin Islands and conducted in the English language
by a single arbitrator pursuant to the rules of the International
Chamber of Commerce (the “Rules”). The arbitrator shall be
appointed in accordance with the procedures set out in the Rules.
The award or decision of the arbitrator shall be final and binding
upon the parties and the parties expressly waive any right under
the laws of any jurisdiction to appeal or otherwise challenge the
award, ruling or decision of the arbitrator. The judgment of any
award or decision may be entered in any court having competent
jurisdiction to the extent necessary. If the Licensor elects to
have a Dispute resolved by arbitration pursuant to this provision,
no party hereto shall (or shall permit its representatives to)
commence, continue or pursue any Dispute in any court.
Notwithstanding anything to the contrary set forth in this
Agreement, the Licensor shall at all times be entitled to obtain
an injunction or injunctions to prevent breaches of this Agreement
and to enforce specifically the terms and provisions thereof, this
being in addition to any other remedy to which the Licensor is
entitled at law or in equity, and the parties hereto hereby waive
the requirement of any undertaking in damages or posting of a bond
in connection with such injunctive relief or specific performance.
EACH PARTY HEREBY WAIVES ITS RIGHTS TO A JURY TRIAL OF ANY CLAIM
OR CAUSE OF ACTION BASED UPON OR ARISING OUT OF THIS AGREEMENT OR
THE SUBJECT MATTER HEREOF. THE SCOPE OF THIS WAIVER IS INTENDED TO
BE ALL-ENCOMPASSING OF ANY AND ALL DISPUTES THAT MAY BE FILED IN
ANY COURT AND THAT RELATE TO THE SUBJECT MATTER OF ANY OF THE
TRANSACTIONS CONTEMPLATED BY THIS AGREEMENT, INCLUDING, WITHOUT
LIMITATION, CONTRACT CLAIMS, TORT CLAIMS (INCLUDING NEGLIGENCE),
BREACH OF DUTY CLAIMS, AND ALL OTHER COMMON LAW AND STATUTORY
CLAIMS. THIS SECTION HAS BEEN FULLY DISCUSSED BY EACH OF THE
PARTIES HERETO AND THESE PROVISIONS WILL NOT BE SUBJECT TO ANY
EXCEPTIONS. EACH PARTY HERETO HEREBY FURTHER WARRANTS AND
REPRESENTS THAT SUCH PARTY HAS REVIEWED THIS WAIVER WITH ITS LEGAL
COUNSEL, AND THAT SUCH PARTY KNOWINGLY AND VOLUNTARILY WAIVES ITS
JURY TRIAL RIGHTS FOLLOWING CONSULTATION WITH LEGAL COUNSEL.
c. Class Action Waiver. No Class Actions Permitted. All Licensees
hereby agree that any arbitration or other permitted action with
respect to any Dispute shall be conducted in their individual
capacities only and not as a class action or other representative
action, and the Licensees expressly waive their right to file a
class action or seek relief on a class basis. LICENSEES SHALL
BRING CLAIMS AGAINST LICENSOR ONLY IN THEIR INDIVIDUAL CAPACITY,
AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR
REPRESENTATIVE PROCEEDING.
Agreements if Class Action Waiver Unenforceable. If any court or
arbitrator makes a final, binding and non-appealable determination
that the class action waiver set forth herein is void or
unenforceable for any reason or that a Dispute can proceed on a
class basis, then the arbitration provision set forth above shall
be deemed null and void with respect to any Dispute that would
thus be required to be resolved by arbitration on a class basis,
and the parties shall be deemed to have not agreed to arbitrate
such Dispute. In the event that, as a result of the application of
the immediately preceding sentence or otherwise, any Dispute is
not subject to arbitration, the parties hereby agree to submit to
the personal and exclusive jurisdiction of and venue in the
federal and state courts located in the British Virgin Islands and
to accept service of process by mail with respect to such Dispute,
and hereby waive any and all jurisdictional and venue defenses
otherwise available with respect to such Dispute.
d. Amendment; Waiver. This Agreement may be amended and provisions
may be waived (either generally or in a particular instance and
either retroactively or prospectively), only with the written
consent of the Licensor.
e. Severability. Any term or provision of this Agreement that is
found invalid or unenforceable in any situation in any
jurisdiction shall not affect the validity or enforceability of
the remaining terms and provisions hereof or the validity or
enforceability of the offending term or provision in any other
situation or in any other jurisdiction. If a final judgment of a
court of competent jurisdiction declares that any term or
provision hereof is invalid or unenforceable, the parties hereto
agree that the court making such determination shall have the
power to limit such term or provision, to delete specific words
or phrases, or to replace any invalid or unenforceable term or
provision with a term or provision that is valid and enforceable
and that comes closest to expressing the intention of the invalid
or unenforceable term or provision, and this Agreement shall be
enforceable as so modified. In the event such court does not
exercise the power granted to it in the prior sentence, the
parties hereto agree to replace such invalid or unenforceable term
or provision with a valid and enforceable term or provision that
will achieve, to the extent possible, the economic, business and
other purposes of such invalid or unenforceable term or provision.
f. Entire Agreement. This Agreement constitutes the entire agreement
and understanding of the parties with respect to the subject matter
hereof and supersede any and all prior negotiations,
correspondence, warrants, agreements, understandings duties or
obligations between or involving the parties with respect to the
subject matter hereof.
g. Delays or Omissions. No delay or omission to exercise any right,
power, or remedy accruing to any party under this Agreement, upon
any breach or default of any other party under this Agreement,
shall impair any such right, power, or remedy of such
non-breaching or non-defaulting party, nor shall it be construed
to be a waiver of or acquiescence to any such breach or default,
or to any similar breach or default thereafter occurring, nor
shall any waiver of any single breach or default be deemed a
waiver of any other breach or default theretofore or thereafter
occurring. Any waiver, permit, consent or approval of any kind or
character on the part of any party of any breach or default under
this Agreement, or any waiver on the part of any party of any
provisions or conditions of this Agreement, must be in writing and
shall be effective only to the extent specifically set forth in
such writing. All remedies, whether under this Agreement or by law
or otherwise afforded to any party, shall be cumulative and not
alternative.
h. Successors and Assigns. The terms and conditions of this Agreement
shall inure to the benefit of and be binding upon the respective
successors and assigns of the parties hereto. This Agreement shall
not have third-party beneficiaries, other than the Martian Council.
i. Rules of Construction. Gender; Etc. For purposes of this
Agreement, whenever the context requires: the singular number
shall include the plural, and vice versa; the masculine gender
shall include the feminine and neuter genders; the feminine gender
shall include the masculine and neuter genders; and the neuter
gender shall include the masculine and feminine genders.
Ambiguities. The Parties hereto agree that any rule of
construction to the effect that ambiguities are to be resolved
against the drafting Party shall not be applied in the
construction or interpretation of this Agreement.
No Limitation. As used in this Agreement, the words “include,”
“including,” “such as” and variations thereof, shall not be deemed
to be terms of limitation, but rather shall be deemed to be
followed by the words “without limitation.” The word “or” shall
mean the non-exclusive “or”. References. Except as otherwise
indicated, all references in this Agreement to “Sections,”
“Schedules” and “Exhibits” are intended to refer to Sections of
this Agreement and Schedules and Exhibits to this Agreement.
Hereof. The terms “hereof,” “herein,” “hereunder,” “hereby” and
“herewith” and words of similar import will, unless otherwise
stated, be construed to refer to this Agreement as a whole and not
to any particular provision of this Agreement.
Captions/Headings. The captions, headings and similar labels
contained in this Agreement are for convenience of reference only,
shall not be deemed to be a part of this Agreement and shall not
be referred to in connection with the construction or
interpretation of this Agreement.
Person. The term “person” refers to any natural born or legal
person, entity, governmental body or incorporated or
unincorporated association, partnership or joint venture.

View File

@ -1,76 +1,32 @@
# Mars Protocol v2 Outpost Frontend
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
![mars-banner-1200w](https://marsprotocol.io/banner.png)
## Getting Started
## 1. Web App
First, install the dependencies:
This project is a [NextJS](https://nextjs.org/). React application.
```
yarn install
```
The project utilises [React hooks](https://reactjs.org/docs/hooks-intro.html), functional components, [Zustand](https://github.com/pmndrs/zustand) for state management, and [useSWR](https://swr.vercel.app/) for general data fetching and management.
Styles are handled with [Tailwind](https://tailwindcss.com/).
Typescript is added and utilised (but optional if you want to create .jsx or .tsx files).
## 2. Deployment
Start web server
Then, run the development server:
```bash
yarn && yarn dev
yarn dev
```
### 2.1 Custom node endpoints using Docker
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
We allow the use of environment variables to be passed to the Docker container to specify custom endpoints for the app. The variables are:
## Trading charts
| Variable | Description | Default |
| ----------------- | ------------------------------------- | ---------------------------------------- |
| URL_OSMOSIS_REST | The Osmosis node REST endpoint to use | https://lcd-osmosis.blockapsis.com |
| URL_OSMOSIS_RPC | The Osmosis node RPC endpoint to use | https://rpc-osmosis.blockapsis.com |
| WALLET_CONNECT_ID | Your projects WalletConnect v2 ID | 0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x |
The trade charts are sourced with the TradingView [charting_library](https://www.tradingview.com/charting-library-docs/). In order to enable the charts, request has to be requested from TradingView. This allows the charting_library package to be pulled-in during the application build process. For this, it is recommended to do the following:
**Sample Docker run command**
1. Request access to the TradingView repository
2. Create a private fork
3. Generate a Personal Access Token from Github
4. Add the following to the environment variables:
a. CHARTING_LIBRARY_USERNAME -> the username of the account with access
b. CHARTING_LIBRARY_ACCESS_TOKEN -> the access token
c. CHARTING_LIBRARY_REPOSITORY -> the URI of the Github repository
5. Build the application by executing the `install_charting_library.sh` script prior.
This command will start the container in interactive mode with port 3000 bound to localhost and print logs to stdout.
```sh
docker run -it -p 3000:3000 \
-e URL_OSMOSIS_REST=https://lcd-osmosis.blockapsis.com \
-e URL_OSMOSIS_RPC=https://rpc-osmosis.blockapsis.com \
-e WALLET_CONNECT_ID=0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x marsprotocol/interface:latest
```
### 2.2 Custom node endpoints using non-Docker deployments
Copy `.env.example` to `.env` and modify the values to suit your needs.
#### 2.2.1 TradingView library
The Outpost UI has a [TradingView](https://www.tradingview.com/) implementation, to display charts for the listed assets.
**TradingView only works on non-docker deployments (e.g. via [vercel](https://vercel.com)). As the docker image already has a fallback installed and can not be updated on `docker run`**
If you are running the UI on a node based server, add the following environment variables to install TradingView.
```
CHARTING_LIBRARY_USERNAME=YOUR_GIT_USERNAME
CHARTING_LIBRARY_ACCESS_TOKEN=YOUR_TRADING_VIEW_ACCESS_TOKEN
CHARTING_LIBRARY_REPOSITORY=github.com/tradingview/charting_library/
```
_NOTE: You'll also need to have an active TradingView license to obtain those credentials_
## 4. Development practices
### 4.1 Data orchestration
Data is handled with a combination of container components, useSWR and a caching mechanism. API hooks are responsible for syncing the application state.
## 5. Contributing
We welcome and encourage contributions! Please create a pull request with as much information about the work you did and what your motivation/intention was.
## 6. License
Contents of this repository are open source under the [Mars Protocol Web Application License Agreement](./LICENSE).
For development on localhost, run `yarn install-charting-library`. Ensure that you have a `.env.local` file defined with the variables mentioned above.

6
__mocks__/fileMock.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
src: '/img.jpg',
height: 24,
width: 24,
blurDataURL: 'data:image/png;base64,imagedata',
}

17
__mocks__/helmet.js Normal file
View File

@ -0,0 +1,17 @@
jest.mock('react-helmet-async', () => {
const React = require('react')
const plugin = jest.requireActual('react-helmet-async')
const mockHelmet = ({ children, ...props }) =>
React.createElement(
'div',
{
...props,
className: 'mock-helmet',
},
children,
)
return {
...plugin,
Helmet: jest.fn().mockImplementation(mockHelmet),
}
})

23
__mocks__/store.js Normal file
View File

@ -0,0 +1,23 @@
jest.mock('store', () => {
let state = {}
const mockUseStore = (selectorFn) => {
return selectorFn(state)
}
mockUseStore.setState = (newState) => {
state = {
...state,
...newState,
}
}
mockUseStore.clearState = () => {
state = {}
}
return {
__esModule: true,
default: mockUseStore,
}
})

1
__mocks__/styleMock.js Normal file
View File

@ -0,0 +1 @@
module.exports = {}

7
__mocks__/svgMock.js Normal file
View File

@ -0,0 +1,7 @@
/* eslint-disable react/display-name */
import React from 'react'
const SvgrMock = React.forwardRef((props, ref) => <svg ref={ref} {...props} />)
export const ReactComponent = SvgrMock
export default SvgrMock

14
__tests__/Footer.test.tsx Normal file
View File

@ -0,0 +1,14 @@
import { render } from '@testing-library/react'
import Footer from 'components/Footer'
import packageJSON from '../package.json'
describe('<Footer />', () => {
it('should render package version correctly', () => {
const { getByText, container } = render(<Footer />)
const versionText = getByText(`v${packageJSON.version}`)
expect(versionText).toBeInTheDocument()
})
})

View File

@ -0,0 +1,67 @@
import { render, screen } from '@testing-library/react'
import AccountDetails from 'components/Account/AccountDetails'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
jest.mock('react-router', () => ({
...(jest.requireActual('react-router') as {}),
useLocation: jest.fn().mockImplementation(() => {
return { pathname: '/testroute' }
}),
}))
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => null))
jest.mock('hooks/useHealthComputer', () =>
jest.fn(() => ({
health: 0,
})),
)
// AccountBalancesTable component has wallet provider dependency, so we mock it
jest.mock('components/Account/AccountBalancesTable', () => jest.fn(() => null))
const mockedUseCurrentAccount = useCurrentAccount as jest.Mock
const mockedAccounts: Account[] = [
{ id: '1', deposits: [], lends: [], debts: [], vaults: [], kind: 'default' },
{ id: '2', deposits: [], lends: [], debts: [], vaults: [], kind: 'default' },
]
jest.mock('hooks/useAccountId', () => jest.fn(() => '1'))
jest.mock('hooks/useAccounts', () =>
jest.fn(() => ({
data: mockedAccounts,
})),
)
jest.mock('hooks/useAccountIds', () =>
jest.fn(() => ({
data: ['1', '2'],
})),
)
jest.mock('hooks/useCurrentAccount', () => jest.fn(() => mockedAccounts[0]))
describe('<AccountDetails />', () => {
beforeAll(() => {
useStore.setState({
address: 'walletAddress',
accounts: mockedAccounts,
})
})
afterAll(() => {
useStore.clearState()
})
it('renders account details WHEN account is selected', () => {
mockedUseCurrentAccount.mockReturnValue(mockedAccounts)
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')
expect(container).toBeInTheDocument()
})
it('does not render WHEN account is NOT selected', () => {
mockedUseCurrentAccount.mockReturnValue(null)
render(<AccountDetails />)
const container = screen.queryByTestId('account-details')
expect(container).not.toBeInTheDocument()
})
})

View File

@ -0,0 +1,157 @@
import { cleanup, render } from '@testing-library/react'
import Button from 'components/Button'
import {
buttonColorClasses,
buttonSizeClasses,
buttonVariantClasses,
focusClasses,
} from 'components/Button/constants'
import { parseMockComponentProps } from 'utils/testing'
jest.mock('components/CircularProgress', () => {
return {
CircularProgress: (props: any) =>
require('utils/testing').createMockComponent('circular-progress-component', props),
}
})
describe('<Button />', () => {
afterAll(() => {
jest.unmock('components/CircularProgress')
})
it('should render', () => {
const { container } = render(<Button />)
expect(container).toBeInTheDocument()
})
it('should render `children` when its passed', () => {
const children = <span data-testid='test-id'>Hello World!</span>
const { getByTestId } = render(<Button>{children}</Button>)
expect(getByTestId('test-id')).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Button className={testClass} />)
expect(container.querySelector('button')).toHaveClass(testClass)
})
it('should handle `color` prop correctly', () => {
const colors = Object.keys(buttonColorClasses) as [keyof typeof buttonColorClasses]
colors.forEach((color) => {
const { container } = render(<Button color={color} />)
expect(container.querySelector('button')).toHaveClass(buttonColorClasses[color])
})
})
it('should handle `disabled=true` prop correctly', () => {
const testFunction = jest.fn()
const { container } = render(<Button disabled={true} onClick={testFunction} />)
const button = container.querySelector('button')
button?.click()
expect(button).toHaveClass('pointer-events-none')
expect(testFunction).not.toBeCalled()
})
it('should handle `disabled=false` prop correctly', () => {
const testFunction = jest.fn()
const { container } = render(<Button disabled={false} onClick={testFunction} />)
const button = container.querySelector('button')
button?.click()
expect(button).not.toHaveClass('pointer-events-none')
expect(testFunction).toBeCalled()
})
it('should show progress indicator when `showProgressIndicator=true`', () => {
const { getByTestId } = render(<Button showProgressIndicator={true} />)
const circularProgressComponent = getByTestId('circular-progress-component')
expect(circularProgressComponent).toBeInTheDocument()
})
it('should set correct values for progress indicator size', () => {
const sizeValues = { xs: 8, sm: 10, md: 12, lg: 18 }
Object.entries(sizeValues).forEach(([size, value]) => {
const { getByTestId } = render(
<Button showProgressIndicator={true} size={size as keyof typeof buttonSizeClasses} />,
)
const circularProgressComponent = getByTestId('circular-progress-component')
const sizeProp = parseMockComponentProps(circularProgressComponent).size
expect(sizeProp).toBe(value)
cleanup()
})
})
it('should handle `size` prop correctly', () => {
const sizes = Object.keys(buttonSizeClasses) as [keyof typeof buttonSizeClasses]
sizes.forEach((size) => {
const { container } = render(<Button size={size} />)
expect(container.querySelector('button')).toHaveClass(buttonSizeClasses[size])
})
})
it('should show `text` when its passed', () => {
const text = 'Hello!'
const { getByText } = render(<Button text={text} />)
expect(getByText(text)).toBeInTheDocument()
})
it('should handle `variant` prop correctly', () => {
const variants = Object.keys(buttonVariantClasses) as [keyof typeof buttonVariantClasses]
variants.forEach((variant) => {
const { container } = render(<Button variant={variant} />)
expect(container.querySelector('button')).toHaveClass(buttonVariantClasses[variant])
})
})
it('should show left icon when `leftIcon` prop is passed', () => {
const icon = <span data-testid='left-icon'>this is the left icon</span>
const { getByTestId } = render(<Button leftIcon={icon} />)
expect(getByTestId('left-icon')).toBeInTheDocument()
})
it('should show right icon when `rightIcon` prop is passed', () => {
const icon = <span data-testid='right-icon'>this is the right icon</span>
const { getByTestId } = render(<Button rightIcon={icon} />)
expect(getByTestId('right-icon')).toBeInTheDocument()
})
it('should handle `iconClassName` prop correctly', () => {
const icon = <span data-testid='icon'>just an icon</span>
const { getByTestId } = render(<Button rightIcon={icon} iconClassName='test-icon-class' />)
expect(getByTestId('icon').parentElement).toHaveClass('test-icon-class')
})
it('should show submenu indicator when `hasSubmenu=true`', () => {
const { getByTestId } = render(<Button hasSubmenu={true} />)
expect(getByTestId('button-submenu-indicator')).toBeInTheDocument()
})
it('should set focus classes when `hasFocus=true`', () => {
const { container } = render(<Button hasFocus={true} color='primary' />)
const button = container.querySelector('button')
expect(button).toHaveClass(focusClasses['primary'])
})
})

View File

@ -0,0 +1,60 @@
import { render, screen } from '@testing-library/react'
import Card from 'components/Card'
jest.mock('components/Text', () => {
return {
__esModule: true,
default: (props: any) =>
require('utils/testing').createMockComponent('mock-text-component', props),
}
})
describe('<Card />', () => {
const defaultProps = {
children: <></>,
}
afterAll(() => {
jest.unmock('components/Text')
})
it('should render', () => {
const { container } = render(<Card {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Card {...defaultProps} className={testClass} />)
expect(container.querySelector('section')).toHaveClass(testClass)
})
it('should handle `contentClassName` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Card {...defaultProps} contentClassName={testClass} />)
expect(container.querySelector('div')).toHaveClass(testClass)
})
it('should handle `title` prop as string correctly', () => {
const testTitle = 'this-is-the-test-title'
const { queryByText } = render(<Card {...defaultProps} title={testTitle} />)
expect(queryByText(testTitle)).toBeInTheDocument()
})
it('should handle `title` prop as element correctly', () => {
const testTitle = <p data-testid='test-title'>Test title</p>
const { queryByTestId } = render(<Card {...defaultProps} title={testTitle} />)
expect(queryByTestId('test-title')).toBeInTheDocument()
expect(queryByTestId('mock-text-component')).not.toBeInTheDocument()
})
it('should handle `id` prop as element correctly', () => {
const testId = 'test-id'
const { container } = render(<Card {...defaultProps} id={testId} />)
expect(container.querySelector(`section#${testId}`)).toBeInTheDocument()
})
})

View File

@ -0,0 +1,34 @@
import { render } from '@testing-library/react'
import { CircularProgress } from 'components/CircularProgress'
import { LocalStorageKeys } from 'constants/localStorageKeys'
describe('<CircularProgress />', () => {
afterAll(() => {
localStorage.removeItem(LocalStorageKeys.REDUCE_MOTION)
})
it('should render', () => {
const { container } = render(<CircularProgress />)
expect(container).toBeInTheDocument()
})
it('should render `...` when animations disabled', () => {
localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'true')
const { getByText } = render(<CircularProgress />)
const threeDots = getByText('...')
expect(threeDots).toBeInTheDocument()
})
it('should render the component with animation classes when animations enabled', () => {
localStorage.setItem(LocalStorageKeys.REDUCE_MOTION, 'false')
const { container } = render(<CircularProgress />)
const progressWithAnimations = container.querySelector('.animate-progress')
expect(progressWithAnimations).toBeInTheDocument()
})
})

View File

@ -0,0 +1,65 @@
import { render } from '@testing-library/react'
import Modal from 'components/Modal'
import UnlockModal from 'components/Modals/Unlock'
import { BN_ONE, BN_ZERO } from 'constants/math'
import { TESTNET_VAULTS_META_DATA } from 'constants/vaults'
import useStore from 'store'
import { BN } from 'utils/helpers'
jest.mock('components/Modal')
const mockedModal = jest.mocked(Modal).mockImplementation(() => <div>Mock</div>)
const mockedDepositedVault: DepositedVault = {
...TESTNET_VAULTS_META_DATA[0],
status: 'active',
apy: 1,
apr: null,
ltv: {
max: 0.65,
liq: 0.7,
},
amounts: {
primary: BN_ONE,
secondary: BN_ONE,
locked: BN_ONE,
unlocked: BN_ONE,
unlocking: BN_ONE,
},
values: {
primary: BN_ZERO,
secondary: BN_ZERO,
unlocked: BN_ZERO,
unlocking: BN_ZERO,
},
cap: {
denom: 'mock',
max: BN(10),
used: BN_ONE,
},
}
describe('<UnlockModal />', () => {
afterAll(() => {
useStore.clearState()
})
it('should render', () => {
const { container } = render(<UnlockModal />)
expect(container).toBeInTheDocument()
})
describe('should set content correctly', () => {
it('should have no content when no modal is present in store', () => {
useStore.setState({ unlockModal: null })
render(<UnlockModal />)
expect(mockedModal).toHaveBeenCalledTimes(0)
})
it('should have content when modal is present in store', () => {
useStore.setState({ unlockModal: { vault: mockedDepositedVault } })
render(<UnlockModal />)
expect(mockedModal).toHaveBeenCalledTimes(1)
})
})
})

View File

@ -0,0 +1,32 @@
import { render } from '@testing-library/react'
import * as rrd from 'react-router-dom'
import PageMetadata from 'components/PageMetadata'
import PAGE_METADATA from 'constants/pageMetadata'
jest.mock('react-router-dom')
const mockedUseLocation = rrd.useLocation as jest.Mock
describe('<PageMetadata />', () => {
afterAll(() => {
jest.clearAllMocks()
})
Object.keys(PAGE_METADATA).forEach((page) => {
it(`should render correct ${page} metadata`, () => {
const pageKey = page as keyof typeof PAGE_METADATA
const pageMetadata = PAGE_METADATA[pageKey]
mockedUseLocation.mockReturnValue({ pathname: pageKey })
const { container } = render(<PageMetadata />)
const titleElement = container.querySelector('title')
const descriptionElement = container.querySelector('meta[name="description"]')
const keywordsElement = container.querySelector('meta[name="keywords"]')
expect(titleElement).toHaveTextContent(pageMetadata.title)
expect(descriptionElement).toHaveAttribute('content', pageMetadata.description)
expect(keywordsElement).toHaveAttribute('content', pageMetadata.keywords)
})
})
})

View File

@ -0,0 +1,98 @@
import { fireEvent, render } from '@testing-library/react'
import BigNumber from 'bignumber.js'
import TokenInput from 'components/TokenInput'
import { ASSETS } from 'constants/assets'
jest.mock('components/DisplayCurrency', () => {
return {
__esModule: true,
default: (props: any) =>
require('utils/testing').createMockComponent('mock-display-currency-component', props),
}
})
describe('<TokenInput />', () => {
const asset = ASSETS[0]
const defaultProps = {
amount: new BigNumber(1),
asset,
max: new BigNumber(100),
onChangeAsset: jest.fn(),
onChange: jest.fn(),
}
afterAll(() => {
jest.unmock('components/DisplayCurrency')
})
it('should render', () => {
const { container } = render(<TokenInput warningMessages={[]} {...defaultProps} />)
expect(container).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} className={testClass} />,
)
expect(getByTestId('token-input-component')).toHaveClass(testClass)
})
it('should handle `disabled` prop correctly', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} disabled={true} />,
)
expect(getByTestId('token-input-component')).toHaveClass('pointer-events-none', 'opacity-50')
})
it('should handle `maxText` prop correctly', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='Max' />,
)
expect(getByTestId('token-input-max-button')).toBeInTheDocument()
})
describe('should render the max button', () => {
it('when `maxText` prop is defined', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='Max' />,
)
expect(getByTestId('token-input-max-button')).toBeInTheDocument()
})
it('not when `maxText` prop is undefined', () => {
const { queryByTestId } = render(<TokenInput warningMessages={[]} {...defaultProps} />)
expect(queryByTestId('token-input-max-button')).not.toBeInTheDocument()
})
})
describe('should render <Select />', () => {
it('when `hasSelect` prop is true and balances is defined', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} balances={[]} hasSelect />,
)
expect(getByTestId('select-component')).toBeInTheDocument()
})
it('not when `hasSelect` prop is true and balances is not defined', () => {
const { queryByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} hasSelect />,
)
expect(queryByTestId('select-component')).not.toBeInTheDocument()
})
it('not when `hasSelect` prop is false and balances is defined', () => {
const { queryByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} balances={[]} />,
)
expect(queryByTestId('select-component')).not.toBeInTheDocument()
})
})
it('should call onMaxBtnClick when the user clicks on max button', () => {
const { getByTestId } = render(
<TokenInput warningMessages={[]} {...defaultProps} maxText='max' />,
)
const maxBtn = getByTestId('token-input-max-button')
fireEvent.click(maxBtn)
expect(defaultProps.onChange).toBeCalledWith(defaultProps.max)
})
})

View File

@ -0,0 +1,45 @@
import { render } from '@testing-library/react'
import { Tooltip } from 'components/Tooltip'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `children` prop correctly', () => {
const { getByTestId } = render(
<Tooltip {...defaultProps} type='info'>
<p data-testid='test-child'>Test text</p>
</Tooltip>,
)
expect(getByTestId('test-child')).toBeInTheDocument()
})
it('should handle `className` prop correctly', () => {
const testClass = 'test-class'
const { container } = render(<Tooltip {...defaultProps} type='info' className={testClass} />)
expect(container.getElementsByClassName(testClass)).toHaveLength(1)
})
describe('should handle `underline` prop correctly', () => {
it('should have border class when children are passed', () => {
const { container } = render(
<Tooltip {...defaultProps} type='info' underline>
<></>
</Tooltip>,
)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(1)
})
it('should not have border class when children are passed', () => {
const { container } = render(<Tooltip {...defaultProps} type='info' underline />)
expect(container.getElementsByClassName('border-b-1')).toHaveLength(0)
})
})
})

View File

@ -0,0 +1,43 @@
import { render } from '@testing-library/react'
import TooltipContent from 'components/Tooltip/TooltipContent'
describe('<Tooltip />', () => {
const defaultProps = {
content: <></>,
}
it('should render', () => {
const { container } = render(<TooltipContent {...defaultProps} type='info' />)
expect(container).toBeInTheDocument()
})
it('should handle `type` prop correctly', () => {
const types = { info: 'bg-white/20', warning: 'bg-warning', error: 'bg-error' }
Object.entries(types).forEach(([key, value]) => {
const { container } = render(<TooltipContent {...defaultProps} type={key as TooltipType} />)
expect(container.getElementsByClassName(value)).toHaveLength(1)
})
})
describe('should handle `content` props correctly', () => {
it('should render Text component when type is string', () => {
const testText = 'testText'
const { getByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testText} />,
)
const textComponent = getByTestId('text-component')
expect(textComponent).toHaveTextContent(testText)
})
it('should render content when type is ReactNode', () => {
const testNode = <p data-testid='test-node'>Test node</p>
const { queryByTestId } = render(
<TooltipContent {...defaultProps} type='info' content={testNode} />,
)
expect(queryByTestId('text-component')).not.toBeInTheDocument()
expect(queryByTestId('test-node')).toBeInTheDocument()
})
})
})

View File

@ -1,29 +0,0 @@
#!/bin/bash
# no verbose
set +x
nextFolder='/app/.next'
# create the config file from environment variables
envFilename='override.conf'
echo "APP_NEXT_OSMOSIS_RPC=$URL_OSMOSIS_RPC" >> $envFilename
echo "APP_NEXT_OSMOSIS_REST=$URL_OSMOSIS_REST" >> $envFilename
echo "APP_NEXT_WALLET_CONNECT_ID=$WALLET_CONNECT_ID" >> $envFilename
function apply_path {
# read all config file
while read line; do
# no comment or not empty
if [ "${line:0:1}" == "#" ] || [ "${line}" == "" ]; then
continue
fi
# split
configName="$(cut -d'=' -f1 <<<"$line")"
configValue="$(cut -d'=' -f2 <<<"$line")"
# replace all config values in built app with the ones defined in override
find $nextFolder \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#$configName#$configValue#g"
done < $envFilename
}
apply_path
exec "$@"

View File

@ -2,22 +2,6 @@ require('dotenv').config()
const shell = require('shelljs')
const path = require('path')
if (
!process.env.CHARTING_LIBRARY_USERNAME ||
!process.env.CHARTING_LIBRARY_ACCESS_TOKEN ||
!process.env.CHARTING_LIBRARY_REPOSITORY
) {
console.log(
'Charting library credentials are missing. Skipping the install of the charting library.',
'\n\n',
'To install the charting library, please provide the following environment variables:',
'\n',
'CHARTING_LIBRARY_USERNAME, CHARTING_LIBRARY_ACCESS_TOKEN, CHARTING_LIBRARY_REPOSITORY',
)
shell.exec(`sh ` + path.join(__dirname, 'install_dummy_charting_library.sh'))
return
}
shell.exec(
`CHARTING_LIBRARY_USERNAME=${process.env.CHARTING_LIBRARY_USERNAME} ` +
`CHARTING_LIBRARY_ACCESS_TOKEN=${process.env.CHARTING_LIBRARY_ACCESS_TOKEN} ` +

View File

@ -14,8 +14,8 @@ remove_if_directory_exists "$LATEST_HASH"
git clone -q --depth 1 -b "$BRANCH" $REPOSITORY "$LATEST_HASH"
remove_if_directory_exists "public/charting_library"
remove_if_directory_exists "public/datafeeds"
remove_if_directory_exists "public/static/charting_library"
remove_if_directory_exists "public/static/datafeeds"
remove_if_directory_exists "src/utils/charting_library"
remove_if_directory_exists "src/utils/datafeeds"

View File

@ -1,15 +0,0 @@
#!/bin/sh
remove_if_directory_exists() {
if [ -d "$1" ]; then rm -Rf "$1"; fi
}
remove_if_directory_exists "public/charting_library"
remove_if_directory_exists "public/datafeeds"
remove_if_directory_exists "src/utils/charting_library"
remove_if_directory_exists "src/utils/datafeeds"
cp -r src/dummy/charting_library public/
cp -r src/dummy/charting_library src/utils/
cp -r src/dummy/datafeeds public/
cp -r src/dummy/datafeeds src/utils/

60
jest.config.js Normal file
View File

@ -0,0 +1,60 @@
module.exports = {
collectCoverage: true,
coverageProvider: 'v8',
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!<rootDir>/out/**',
'!<rootDir>/.next/**',
'!<rootDir>/*.config.js',
'!<rootDir>/coverage/**',
'!<rootDir>/src/types/**',
'!<rootDir>/src/utils/charting_library/**',
'!<rootDir>/src/utils/datafeeds/**',
'!<rootDir>/public/charting_library/**',
'!<rootDir>/public/datafeeds/**',
'!<rootDir>/src/utils/health_computer/**',
],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// Handle CSS imports (without CSS modules)
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
// Handle image imports
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp)$/i': `<rootDir>/__mocks__/fileMock.js`,
'^.+\\.svg$': `<rootDir>/__mocks__/svgMock.js`,
// Handle module aliases
'^app/(.*)$': '<rootDir>/src/app/$1',
'^api/(.*)$': '<rootDir>/src/api/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^constants/(.*)$': '<rootDir>/src/constants/$1',
'^fonts/(.*)$': '<rootDir>/src/fonts/$1',
'^hooks/(.*)$': '<rootDir>/src/hooks/$1',
'^pages/(.*)$': '<rootDir>/src/pages/$1',
'^store/(.*)$': '<rootDir>/src/store/$1',
'^styles/(.*)$': '<rootDir>/src/styles/$1',
'^types/(.*)$': '<rootDir>/src/types/$1',
'^utils/(.*)$': '<rootDir>/src/utils/$1',
'^store': '<rootDir>/src/store',
},
// Add more setup options before each test is run
setupFilesAfterEnv: [
'<rootDir>/jest.setup.js',
'<rootDir>/__mocks__/store.js',
'<rootDir>/__mocks__/helmet.js',
],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
}

1
jest.setup.js Normal file
View File

@ -0,0 +1 @@
import '@testing-library/jest-dom/extend-expect'

View File

@ -1,12 +0,0 @@
diff --git a/next.config.js b/next.config.js
index 4a208ebb..9c716638 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
+ output: 'standalone',
reactStrictMode: true,
images: {
domains: [

View File

@ -69,4 +69,18 @@ const nextConfig = {
},
}
const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that
// the following options are set automatically, and overriding them is not
// recommended:
// release, url, org, project, authToken, configFile, stripPrefix,
// urlPrefix, include, ignore
silent: true, // Suppresses all logs
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options.
}
// Make sure adding Sentry options is the last code to run before exporting, to
// ensure that your source maps include changes from all other Webpack plugins
module.exports = nextConfig

View File

@ -1,82 +1,86 @@
{
"name": "mars-v2-frontend",
"version": "2.2.4",
"homepage": "./",
"private": false,
"license": "SEE LICENSE IN LICENSE FILE",
"version": "2.0.5",
"private": true,
"scripts": {
"build": "yarn install-charting-library && next build",
"build": "yarn validate-env && next build",
"dev": "next dev",
"test": "jest",
"test:cov": "jest --coverage",
"lint": "eslint ./src/ && yarn prettier-check",
"format": "eslint ./src/ --fix && prettier --write ./src/ ",
"format": "eslint ./src/ ./__tests__/ --fix && prettier --write ./src/ ./__tests__/",
"prettier-check": "prettier --ignore-path .gitignore --check ./src/",
"start": "next start",
"install-charting-library": "dotenv -e .env.local node install_charting_library.js"
"validate-env": "node ./validate-env",
"install-charting-library": "dotenv -e .env.local node install_charting_library.js",
"prepare": "husky install"
},
"lint-staged": {
"*.ts*": [
"eslint ./src/ --fix",
"prettier --write ./src/"
"eslint ./src/ ./__tests__/ --fix",
"prettier --write ./src/ ./__tests__/"
]
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.32.2",
"@delphi-labs/shuttle-react": "^3.19.1",
"@keplr-wallet/cosmos": "^0.12.67",
"@cosmjs/cosmwasm-stargate": "^0.31.1",
"@delphi-labs/shuttle-react": "^3.10.0",
"@keplr-wallet/cosmos": "^0.12.42",
"@sentry/nextjs": "^7.77.0",
"@splinetool/react-spline": "^2.2.6",
"@splinetool/runtime": "^1.0.52",
"@splinetool/runtime": "^0.9.482",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-table": "^8.11.8",
"@tanstack/react-table": "^8.10.6",
"@tippyjs/react": "^4.2.6",
"bignumber.js": "^9.1.2",
"classnames": "^2.5.1",
"classnames": "^2.3.2",
"debounce-promise": "^3.1.2",
"ibc-domains-sdk": "^1.1.0",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"moment": "^2.30.1",
"next": "^14.1.0",
"moment": "^2.29.4",
"next": "^13.5.4",
"react": "^18.2.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-helmet-async": "^2.0.4",
"react-helmet-async": "^1.3.0",
"react-qr-code": "^2.0.12",
"react-router-dom": "^6.22.0",
"react-router-dom": "^6.17.0",
"react-spring": "^9.7.3",
"react-toastify": "^10.0.4",
"react-toastify": "^9.1.3",
"react-use-clipboard": "^1.0.9",
"recharts": "^2.12.0",
"recharts": "^2.10.1",
"swr": "^2.2.4",
"tailwind-scrollbar-hide": "^1.1.7",
"zustand": "^4.5.0"
"zustand": "^4.4.6"
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
"@types/debounce-promise": "^3.1.9",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@types/debounce-promise": "^3.1.7",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^20.11.17",
"@types/react": "18.2.55",
"@types/react-dom": "18.2.19",
"@types/react-helmet": "^6.1.11",
"autoprefixer": "^10.4.17",
"dotenv": "^16.4.3",
"@types/lodash.throttle": "^4.1.8",
"@types/node": "^20.8.6",
"@types/react": "18.2.33",
"@types/react-dom": "18.2.15",
"@types/react-helmet": "^6.1.9",
"autoprefixer": "^10.4.16",
"babel-jest": "^29.7.0",
"dotenv": "^16.3.1",
"dotenv-cli": "^7.3.0",
"eslint": "^8.56.0",
"eslint-config-next": "^14.1.0",
"eslint-plugin-import": "^2.29.1",
"husky": "^9.0.10",
"eslint": "^8.54.0",
"eslint-config-next": "^14.0.0",
"eslint-plugin-import": "^2.29.0",
"husky": "^8.0.3",
"identity-obj-proxy": "^3.0.0",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6",
"shelljs": "^0.8.5",
"tailwindcss": "^3.4.1",
"typescript": "5.3.3"
},
"engines": {
"npm": "please-use-yarn",
"yarn": ">= 1.19.1"
"tailwindcss": "^3.3.3",
"typescript": "5.2.2"
}
}

View File

@ -1,4 +1,4 @@
<svg version="1.1" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M278.2 77L630.637 686L748 482.977L513.1 77H278.2V77Z" fill="#F34C2D" />
<path d="M278.2 494.6L400.087 686H643.6L521.887 494.6H278.2V494.6Z" fill="#BE312D" />
<path d="M173.8 268.4L52 477.2L173.887 686L417.4 268.4H173.8Z" fill="#F34C2D" />

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 366 B

View File

@ -1,4 +1,4 @@
<svg version="1.1" viewBox="0 0 501 501" xmlns="http://www.w3.org/2000/svg">
<svg width="501" height="501" viewBox="0 0 501 501" xmlns="http://www.w3.org/2000/svg">
<path
d="M250.75 500.75C388.82 500.75 500.75 388.82 500.75 250.75C500.75 112.679 388.82 0.75 250.75 0.75C112.679 0.75 0.75 112.679 0.75 250.75C0.75 388.82 112.679 500.75 250.75 500.75Z"
fill="#FFFFFF"

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,4 +1,4 @@
<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 24C18.6499 24 24 18.6499 24 12C24 5.3501 18.6499 0 12 0C5.3501 0 0 5.3501 0 12C0 18.6499 5.3501 24 12 24Z"
fill="#2775CA"

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,4 +1,4 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 600 600">
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" fill="none" viewBox="0 0 600 600">
<path
fill="url(#paint0_linear)"
d="M148.497 169.135C150.981 166.013 153.613 163.009 156.245 160.005C156.363 159.856 156.63 159.826 156.748 159.677C156.985 159.38 157.37 159.201 157.606 158.903L157.843 158.606C159.678 156.91 161.63 155.064 163.881 153.456C171.845 147.41 180.11 142.817 188.825 139.795C216.778 129.981 247.894 136.029 272.295 159.065C306.366 191.002 303.315 242.451 276.117 276.647C241.748 327.625 182.684 398.748 264.463 462.46C279.167 473.916 290.075 483.361 336.392 496.746C306.1 502.326 278.012 500.59 246.748 492.605C224.634 480.123 189.866 453.397 178.037 417.3C160.159 362.562 209.513 280.732 233.365 249.216C266.113 205.599 213.124 158.382 174.112 211.095C153.72 238.566 118.044 316.303 130.442 373.965C137.691 406.664 147.353 430.499 185.663 463.241C178.559 459.049 171.66 454.294 164.968 448.974C75.957 366.06 86.2838 237.859 148.497 169.135Z"

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,114 +1,29 @@
<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z"
style="fill: url(#_Linear1); fill-rule: nonzero"
/>
<path
d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z"
style="fill: none; fill-rule: nonzero; stroke: white; stroke-width: 1px"
/>
<path
d="M11.999,0.166L15.656,0.746L18.955,2.426L21.573,5.044L23.254,8.342L23.834,11.999L23.254,15.656L21.573,18.955L18.955,21.573L15.656,23.254L11.999,23.834L8.343,23.254L5.044,21.573L2.426,18.955L0.746,15.656L0.166,11.999L0.746,8.343L2.426,5.044L5.044,2.426L8.343,0.746L11.999,0.166ZM8.549,1.381L5.436,2.966L2.967,5.436L1.381,8.549L0.834,11.999L1.381,15.45L2.967,18.562L5.436,21.032L8.549,22.619L11.999,23.166L15.45,22.619L18.562,21.032L21.032,18.562L22.619,15.45L23.166,11.999L22.619,8.549L21.032,5.436L18.562,2.966L15.45,1.381L11.999,0.834L8.549,1.381Z"
style="fill: white"
/>
<path
d="M11.915,3.928C11.993,3.881 12.094,3.907 12.141,3.985L14.738,8.343C14.785,8.421 14.759,8.523 14.681,8.569C14.603,8.616 14.501,8.59 14.455,8.512L11.857,4.154C11.811,4.076 11.836,3.975 11.915,3.928Z"
style="fill: white"
/>
<path
d="M11.999,3.904L17.36,4.484L17.325,4.812L11.999,4.236L6.674,4.812L6.639,4.484L11.999,3.904Z"
style="fill: white"
/>
<path
d="M9.237,8.426C9.237,8.334 9.311,8.261 9.402,8.261L14.596,8.261C14.687,8.261 14.761,8.334 14.761,8.426C14.761,8.517 14.687,8.591 14.596,8.591L9.402,8.591C9.311,8.591 9.237,8.517 9.237,8.426Z"
style="fill: white"
/>
<path
d="M12.083,3.928C12.161,3.975 12.187,4.076 12.14,4.154L9.544,8.512C9.497,8.59 9.396,8.616 9.318,8.569C9.24,8.523 9.214,8.421 9.261,8.343L11.857,3.985C11.903,3.907 12.004,3.881 12.083,3.928Z"
style="fill: white"
/>
<path
d="M8.392,0.803L11.999,3.853L15.606,0.803L17.49,4.573L17.195,4.721L15.499,1.326L11.999,4.285L8.499,1.326L6.804,4.721L6.509,4.573L8.392,0.803Z"
style="fill: white"
/>
<path
d="M5.143,2.563C5.217,2.509 5.32,2.525 5.374,2.599L11.999,11.719L18.625,2.599C18.679,2.525 18.782,2.509 18.856,2.563C18.929,2.616 18.946,2.719 18.892,2.793L12.133,12.097C12.102,12.14 12.052,12.165 11.999,12.165C11.947,12.165 11.897,12.14 11.866,12.097L5.107,2.793C5.053,2.719 5.07,2.616 5.143,2.563Z"
style="fill: white"
/>
<path
d="M9.525,8.314C9.565,8.358 9.578,8.42 9.56,8.476L7.992,13.303L12.081,16.274L16.727,18.266C16.811,18.302 16.85,18.399 16.814,18.482C16.778,18.566 16.681,18.605 16.597,18.569L11.934,16.57C11.923,16.565 11.912,16.559 11.902,16.552L7.701,13.499C7.643,13.457 7.619,13.383 7.641,13.315L9.156,8.651L4.493,9.709C4.404,9.729 4.315,9.674 4.295,9.585C4.275,9.496 4.331,9.408 4.42,9.387L9.367,8.265C9.424,8.251 9.485,8.27 9.525,8.314Z"
style="fill: white"
/>
<path
d="M4.347,9.426C4.416,9.366 4.52,9.373 4.58,9.442L7.922,13.259C7.982,13.328 7.975,13.432 7.907,13.492C7.838,13.552 7.734,13.545 7.674,13.477L4.332,9.659C4.272,9.591 4.279,9.486 4.347,9.426Z"
style="fill: white"
/>
<path
d="M6.931,4.439L4.635,9.553L4.661,9.616L4.603,9.652L3.534,14.751L7.435,18.284L12.078,20.948L15.442,22.696L16.535,18.244L21.105,18.581L20.481,14.837L20.807,14.783L21.499,18.941L16.789,18.593L15.662,23.182L11.917,21.236L7.24,18.553L3.238,14.928L0.234,11.971L4.25,9.483L2.464,5.109L6.931,4.439ZM4.215,9.893L0.765,12.03L3.254,14.48L4.215,9.893ZM4.463,9.131L6.382,4.855L2.928,5.373L4.463,9.131Z"
style="fill: white"
/>
<path
d="M7.814,13.202C7.904,13.211 7.971,13.291 7.963,13.382L7.502,18.433C7.494,18.524 7.414,18.591 7.323,18.583C7.232,18.574 7.165,18.494 7.174,18.403L7.634,13.352C7.642,13.261 7.723,13.194 7.814,13.202Z"
style="fill: white"
/>
<path
d="M12.151,16.353C12.187,16.436 12.148,16.533 12.065,16.569L7.403,18.569C7.319,18.604 7.222,18.566 7.186,18.482C7.15,18.398 7.189,18.301 7.273,18.265L11.935,16.266C12.018,16.23 12.115,16.269 12.151,16.353Z"
style="fill: white"
/>
<path
d="M2.893,18.581L3.517,14.837L3.191,14.783L2.499,18.941L7.211,18.594L8.336,23.182L12.075,21.24L11.923,20.947L8.556,22.696L7.465,18.244L2.893,18.581Z"
style="fill: white"
/>
<path
d="M12.097,11.865C12.14,11.896 12.165,11.946 12.165,11.998L12.165,23.5C12.165,23.591 12.091,23.665 12,23.665C11.909,23.665 11.835,23.591 11.835,23.5L11.835,12.226L1.115,15.709C1.029,15.737 0.936,15.69 0.908,15.603C0.879,15.517 0.927,15.423 1.013,15.395L11.949,11.842C11.999,11.825 12.054,11.834 12.097,11.865Z"
style="fill: white"
/>
<path
d="M17.212,4.547C17.248,4.499 17.308,4.475 17.368,4.484L21.327,5.078C21.377,5.085 21.421,5.115 21.446,5.159C21.471,5.203 21.474,5.256 21.455,5.303L19.749,9.483L23.587,11.861C23.63,11.887 23.658,11.932 23.664,11.982C23.669,12.033 23.652,12.083 23.616,12.118L20.762,14.926L20.756,14.932L20.755,14.932C20.755,14.933 20.754,14.934 20.753,14.934L16.773,18.541C16.727,18.583 16.661,18.595 16.602,18.572C16.544,18.55 16.504,18.496 16.498,18.434L16.039,13.4L14.44,8.478C14.421,8.421 14.435,8.359 14.475,8.315C14.515,8.272 14.575,8.253 14.633,8.266L19.258,9.315L17.193,4.715C17.168,4.659 17.175,4.595 17.212,4.547ZM19.237,9.649L14.843,8.652L16.269,13.04L19.237,9.649ZM16.372,13.422L16.797,18.074L20.464,14.751L19.45,9.907L16.372,13.422ZM19.784,9.893L20.744,14.48L23.234,12.03L19.784,9.893ZM19.536,9.13L21.07,5.373L17.617,4.855L19.536,9.13Z"
style="fill: white"
/>
<path
d="M11.842,11.947C11.87,11.861 11.963,11.813 12.05,11.842L22.987,15.395C23.074,15.423 23.121,15.517 23.093,15.603C23.065,15.69 22.972,15.737 22.885,15.709L11.948,12.155C11.861,12.127 11.814,12.034 11.842,11.947Z"
style="fill: white"
/>
<path
d="M16.335,13.27C16.389,13.343 16.372,13.447 16.298,13.5L12.096,16.552C12.022,16.606 11.919,16.59 11.866,16.516C11.812,16.442 11.828,16.339 11.902,16.285L16.104,13.233C16.178,13.18 16.281,13.196 16.335,13.27Z"
style="fill: white"
/>
<path
d="M16.58,18.276C16.653,18.234 16.746,18.254 16.796,18.322L18.892,21.208C18.945,21.282 18.929,21.385 18.855,21.439C18.782,21.492 18.678,21.476 18.625,21.402L16.616,18.636L12.081,21.237C12.002,21.282 11.901,21.255 11.856,21.176C11.81,21.097 11.838,20.996 11.917,20.951L16.58,18.276Z"
style="fill: white"
/>
<path
d="M11.999,0.335C12.09,0.335 12.164,0.409 12.164,0.5L12.164,4.068C12.164,4.159 12.09,4.233 11.999,4.233C11.908,4.233 11.834,4.159 11.834,4.068L11.834,0.5C11.834,0.409 11.908,0.335 11.999,0.335Z"
style="fill: white"
/>
<path
d="M23.093,8.396C23.121,8.483 23.073,8.576 22.987,8.604L19.594,9.706C19.507,9.735 19.414,9.687 19.386,9.601C19.358,9.514 19.405,9.421 19.492,9.393L22.885,8.29C22.971,8.262 23.064,8.309 23.093,8.396Z"
style="fill: white"
/>
<path
d="M7.435,18.286C7.509,18.34 7.525,18.443 7.471,18.516L5.374,21.402C5.32,21.476 5.217,21.492 5.143,21.439C5.069,21.385 5.053,21.282 5.107,21.208L7.204,18.322C7.258,18.249 7.361,18.232 7.435,18.286Z"
style="fill: white"
/>
<path
d="M0.908,8.396C0.936,8.309 1.029,8.262 1.115,8.29L4.508,9.393C4.595,9.421 4.642,9.514 4.614,9.601C4.586,9.687 4.493,9.735 4.406,9.706L1.013,8.604C0.927,8.576 0.879,8.483 0.908,8.396Z"
style="fill: white"
/>
<defs>
<linearGradient
id="_Linear1"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-14.8673,-14.7971,14.7971,-14.8673,20.4244,16.9042)"
>
<stop offset="0" style="stop-color: rgb(239, 65, 54); stop-opacity: 1" />
<stop offset="0.01" style="stop-color: rgb(239, 65, 54); stop-opacity: 1" />
<stop offset="0.32" style="stop-color: rgb(223, 81, 83); stop-opacity: 1" />
<stop offset="1" style="stop-color: rgb(172, 11, 27); stop-opacity: 1" />
</linearGradient>
</defs>
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
<path d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
<path d="M12.985,0.812C13.949,0.812 14.279,0.928 14.929,1.155C15.091,1.211 15.273,1.275 15.49,1.345C19.947,2.795 23.25,7.071 23.25,12.005C23.25,12.829 23.133,13.721 22.963,14.506C22.196,17.622 20.213,20.269 17.543,21.804C16.213,22.568 14.67,23.131 13.068,23.239C12.8,23.257 11.93,23.249 11.339,23.243C11.126,23.241 10.95,23.239 10.85,23.239C5.061,22.419 0.75,18.142 0.75,12.005C0.75,10.447 1.047,8.887 1.653,7.454C2.282,5.968 3.362,4.699 4.486,3.6C5.476,2.634 6.7,2.1 7.906,1.574C8.081,1.498 8.255,1.422 8.428,1.345C9.05,1.068 9.469,1.008 9.996,0.932C10.214,0.9 10.45,0.866 10.727,0.812C11.214,0.719 11.726,0.75 12.234,0.781C12.486,0.797 12.738,0.812 12.985,0.812Z" style="fill:none;fill-rule:nonzero;stroke:white;stroke-width:1px;"/>
<path d="M11.999,0.166L15.656,0.746L18.955,2.426L21.573,5.044L23.254,8.342L23.834,11.999L23.254,15.656L21.573,18.955L18.955,21.573L15.656,23.254L11.999,23.834L8.343,23.254L5.044,21.573L2.426,18.955L0.746,15.656L0.166,11.999L0.746,8.343L2.426,5.044L5.044,2.426L8.343,0.746L11.999,0.166ZM8.549,1.381L5.436,2.966L2.967,5.436L1.381,8.549L0.834,11.999L1.381,15.45L2.967,18.562L5.436,21.032L8.549,22.619L11.999,23.166L15.45,22.619L18.562,21.032L21.032,18.562L22.619,15.45L23.166,11.999L22.619,8.549L21.032,5.436L18.562,2.966L15.45,1.381L11.999,0.834L8.549,1.381Z" style="fill:white;"/>
<path d="M11.915,3.928C11.993,3.881 12.094,3.907 12.141,3.985L14.738,8.343C14.785,8.421 14.759,8.523 14.681,8.569C14.603,8.616 14.501,8.59 14.455,8.512L11.857,4.154C11.811,4.076 11.836,3.975 11.915,3.928Z" style="fill:white;"/>
<path d="M11.999,3.904L17.36,4.484L17.325,4.812L11.999,4.236L6.674,4.812L6.639,4.484L11.999,3.904Z" style="fill:white;"/>
<path d="M9.237,8.426C9.237,8.334 9.311,8.261 9.402,8.261L14.596,8.261C14.687,8.261 14.761,8.334 14.761,8.426C14.761,8.517 14.687,8.591 14.596,8.591L9.402,8.591C9.311,8.591 9.237,8.517 9.237,8.426Z" style="fill:white;"/>
<path d="M12.083,3.928C12.161,3.975 12.187,4.076 12.14,4.154L9.544,8.512C9.497,8.59 9.396,8.616 9.318,8.569C9.24,8.523 9.214,8.421 9.261,8.343L11.857,3.985C11.903,3.907 12.004,3.881 12.083,3.928Z" style="fill:white;"/>
<path d="M8.392,0.803L11.999,3.853L15.606,0.803L17.49,4.573L17.195,4.721L15.499,1.326L11.999,4.285L8.499,1.326L6.804,4.721L6.509,4.573L8.392,0.803Z" style="fill:white;"/>
<path d="M5.143,2.563C5.217,2.509 5.32,2.525 5.374,2.599L11.999,11.719L18.625,2.599C18.679,2.525 18.782,2.509 18.856,2.563C18.929,2.616 18.946,2.719 18.892,2.793L12.133,12.097C12.102,12.14 12.052,12.165 11.999,12.165C11.947,12.165 11.897,12.14 11.866,12.097L5.107,2.793C5.053,2.719 5.07,2.616 5.143,2.563Z" style="fill:white;"/>
<path d="M9.525,8.314C9.565,8.358 9.578,8.42 9.56,8.476L7.992,13.303L12.081,16.274L16.727,18.266C16.811,18.302 16.85,18.399 16.814,18.482C16.778,18.566 16.681,18.605 16.597,18.569L11.934,16.57C11.923,16.565 11.912,16.559 11.902,16.552L7.701,13.499C7.643,13.457 7.619,13.383 7.641,13.315L9.156,8.651L4.493,9.709C4.404,9.729 4.315,9.674 4.295,9.585C4.275,9.496 4.331,9.408 4.42,9.387L9.367,8.265C9.424,8.251 9.485,8.27 9.525,8.314Z" style="fill:white;"/>
<path d="M4.347,9.426C4.416,9.366 4.52,9.373 4.58,9.442L7.922,13.259C7.982,13.328 7.975,13.432 7.907,13.492C7.838,13.552 7.734,13.545 7.674,13.477L4.332,9.659C4.272,9.591 4.279,9.486 4.347,9.426Z" style="fill:white;"/>
<path d="M6.931,4.439L4.635,9.553L4.661,9.616L4.603,9.652L3.534,14.751L7.435,18.284L12.078,20.948L15.442,22.696L16.535,18.244L21.105,18.581L20.481,14.837L20.807,14.783L21.499,18.941L16.789,18.593L15.662,23.182L11.917,21.236L7.24,18.553L3.238,14.928L0.234,11.971L4.25,9.483L2.464,5.109L6.931,4.439ZM4.215,9.893L0.765,12.03L3.254,14.48L4.215,9.893ZM4.463,9.131L6.382,4.855L2.928,5.373L4.463,9.131Z" style="fill:white;"/>
<path d="M7.814,13.202C7.904,13.211 7.971,13.291 7.963,13.382L7.502,18.433C7.494,18.524 7.414,18.591 7.323,18.583C7.232,18.574 7.165,18.494 7.174,18.403L7.634,13.352C7.642,13.261 7.723,13.194 7.814,13.202Z" style="fill:white;"/>
<path d="M12.151,16.353C12.187,16.436 12.148,16.533 12.065,16.569L7.403,18.569C7.319,18.604 7.222,18.566 7.186,18.482C7.15,18.398 7.189,18.301 7.273,18.265L11.935,16.266C12.018,16.23 12.115,16.269 12.151,16.353Z" style="fill:white;"/>
<path d="M2.893,18.581L3.517,14.837L3.191,14.783L2.499,18.941L7.211,18.594L8.336,23.182L12.075,21.24L11.923,20.947L8.556,22.696L7.465,18.244L2.893,18.581Z" style="fill:white;"/>
<path d="M12.097,11.865C12.14,11.896 12.165,11.946 12.165,11.998L12.165,23.5C12.165,23.591 12.091,23.665 12,23.665C11.909,23.665 11.835,23.591 11.835,23.5L11.835,12.226L1.115,15.709C1.029,15.737 0.936,15.69 0.908,15.603C0.879,15.517 0.927,15.423 1.013,15.395L11.949,11.842C11.999,11.825 12.054,11.834 12.097,11.865Z" style="fill:white;"/>
<path d="M17.212,4.547C17.248,4.499 17.308,4.475 17.368,4.484L21.327,5.078C21.377,5.085 21.421,5.115 21.446,5.159C21.471,5.203 21.474,5.256 21.455,5.303L19.749,9.483L23.587,11.861C23.63,11.887 23.658,11.932 23.664,11.982C23.669,12.033 23.652,12.083 23.616,12.118L20.762,14.926L20.756,14.932L20.755,14.932C20.755,14.933 20.754,14.934 20.753,14.934L16.773,18.541C16.727,18.583 16.661,18.595 16.602,18.572C16.544,18.55 16.504,18.496 16.498,18.434L16.039,13.4L14.44,8.478C14.421,8.421 14.435,8.359 14.475,8.315C14.515,8.272 14.575,8.253 14.633,8.266L19.258,9.315L17.193,4.715C17.168,4.659 17.175,4.595 17.212,4.547ZM19.237,9.649L14.843,8.652L16.269,13.04L19.237,9.649ZM16.372,13.422L16.797,18.074L20.464,14.751L19.45,9.907L16.372,13.422ZM19.784,9.893L20.744,14.48L23.234,12.03L19.784,9.893ZM19.536,9.13L21.07,5.373L17.617,4.855L19.536,9.13Z" style="fill:white;"/>
<path d="M11.842,11.947C11.87,11.861 11.963,11.813 12.05,11.842L22.987,15.395C23.074,15.423 23.121,15.517 23.093,15.603C23.065,15.69 22.972,15.737 22.885,15.709L11.948,12.155C11.861,12.127 11.814,12.034 11.842,11.947Z" style="fill:white;"/>
<path d="M16.335,13.27C16.389,13.343 16.372,13.447 16.298,13.5L12.096,16.552C12.022,16.606 11.919,16.59 11.866,16.516C11.812,16.442 11.828,16.339 11.902,16.285L16.104,13.233C16.178,13.18 16.281,13.196 16.335,13.27Z" style="fill:white;"/>
<path d="M16.58,18.276C16.653,18.234 16.746,18.254 16.796,18.322L18.892,21.208C18.945,21.282 18.929,21.385 18.855,21.439C18.782,21.492 18.678,21.476 18.625,21.402L16.616,18.636L12.081,21.237C12.002,21.282 11.901,21.255 11.856,21.176C11.81,21.097 11.838,20.996 11.917,20.951L16.58,18.276Z" style="fill:white;"/>
<path d="M11.999,0.335C12.09,0.335 12.164,0.409 12.164,0.5L12.164,4.068C12.164,4.159 12.09,4.233 11.999,4.233C11.908,4.233 11.834,4.159 11.834,4.068L11.834,0.5C11.834,0.409 11.908,0.335 11.999,0.335Z" style="fill:white;"/>
<path d="M23.093,8.396C23.121,8.483 23.073,8.576 22.987,8.604L19.594,9.706C19.507,9.735 19.414,9.687 19.386,9.601C19.358,9.514 19.405,9.421 19.492,9.393L22.885,8.29C22.971,8.262 23.064,8.309 23.093,8.396Z" style="fill:white;"/>
<path d="M7.435,18.286C7.509,18.34 7.525,18.443 7.471,18.516L5.374,21.402C5.32,21.476 5.217,21.492 5.143,21.439C5.069,21.385 5.053,21.282 5.107,21.208L7.204,18.322C7.258,18.249 7.361,18.232 7.435,18.286Z" style="fill:white;"/>
<path d="M0.908,8.396C0.936,8.309 1.029,8.262 1.115,8.29L4.508,9.393C4.595,9.421 4.642,9.514 4.614,9.601C4.586,9.687 4.493,9.735 4.406,9.706L1.013,8.604C0.927,8.576 0.879,8.483 0.908,8.396Z" style="fill:white;"/>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-14.8673,-14.7971,14.7971,-14.8673,20.4244,16.9042)"><stop offset="0" style="stop-color:rgb(239,65,54);stop-opacity:1"/><stop offset="0.01" style="stop-color:rgb(239,65,54);stop-opacity:1"/><stop offset="0.32" style="stop-color:rgb(223,81,83);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(172,11,27);stop-opacity:1"/></linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -1,41 +0,0 @@
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask
id="mask0_1016_8"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="200"
height="200"
>
<circle cx="100" cy="100" r="100" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_1016_8)">
<path d="M200 0H0V200H200V0Z" fill="url(#paint0_radial_1016_8)" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M17 100C17 54.1604 54.1604 17 100 17C145.84 17 183 54.1604 183 100C183 145.84 145.84 183 100 183C54.1604 183 17 145.84 17 100ZM35.9934 70.1292C35.7605 69.0522 34.2896 68.8451 33.8321 69.8475C29.6396 79.0329 27.3034 89.2436 27.3034 100C27.3034 110.756 29.6395 120.967 33.8321 130.152C34.2896 131.155 35.7646 130.946 36.0113 129.872C36.7592 126.616 37.8122 123.265 39.1296 119.865C46.1201 101.826 58.4843 85.6661 72.0752 72.0752C77.7197 66.4307 83.5473 61.3328 89.398 56.8477C90.0458 56.3511 89.9903 55.355 89.2818 54.9498C84.8395 52.4091 80.5176 50.328 76.4118 48.7369C69.6362 46.1112 63.6826 44.9107 58.8762 45.0133C54.1054 45.1152 50.7932 46.4757 48.6345 48.6344C46.23 51.0389 44.8034 54.9033 45.0307 60.5995C45.2441 65.9507 46.9226 72.4804 50.1278 79.7726C50.5169 80.6578 50.3294 81.6944 49.6393 82.3716L45.0105 87.3236C43.9702 88.3445 42.2606 88.1407 41.6138 86.8345C39.236 82.0323 37.0957 75.2258 35.9934 70.1292ZM69.8476 33.832C68.8452 34.2896 69.0539 35.7646 70.1279 36.0113C73.3837 36.7591 76.7352 37.8122 80.1348 39.1296C95.2954 45.0046 112.35 56.5003 127.925 72.0752C132.56 76.7098 137.571 85.0812 139.074 94.9299C139.238 96.0021 140.672 96.3736 141.265 95.4651C145.577 88.8521 148.942 82.402 151.263 76.4117C153.889 69.6362 155.089 63.6826 154.987 58.8761C154.885 54.1054 153.524 50.7932 151.366 48.6344C148.907 46.1759 144.657 44.6798 138.75 44.9907C133.226 45.2815 127.994 46.6016 120.702 49.9344C119.733 50.3773 118.597 50.2673 117.745 49.6276L112.297 45.4006C111.335 44.6781 111.397 43.2145 112.428 42.5945C118.524 38.9293 123.663 36.6594 128.563 35.7699C129.744 35.5554 130.094 33.7945 128.993 33.3153C120.111 29.448 110.306 27.3034 100 27.3034C89.2437 27.3034 79.033 29.6395 69.8476 33.832ZM120.617 120.661C131.639 109.325 132.032 90.7538 120.639 79.3608C116.409 75.131 112.092 71.2466 107.765 67.7333C103.229 64.0492 96.7712 64.0493 92.2349 67.7333C87.9021 71.2521 83.5841 75.1376 79.3608 79.3608C68.5831 90.1385 68.3317 107.296 77.8289 118.936C88.3068 131.778 108.966 132.271 120.617 120.661ZM104.999 139.012C103.917 139.162 103.531 140.608 104.445 141.206C111.09 145.547 117.571 148.931 123.588 151.263C130.364 153.889 136.318 155.089 141.124 154.987C145.895 154.885 149.207 153.524 151.366 151.366C153.77 148.961 155.197 145.097 154.969 139.401C154.758 134.088 153.117 127.729 149.959 120.517C149.521 119.517 149.664 118.353 150.352 117.506L155.226 111.506C155.948 110.618 157.319 110.673 157.903 111.656C161.275 117.325 163.21 124.157 164.129 129.394C164.326 130.52 165.886 130.784 166.354 129.742C170.429 120.663 172.697 110.596 172.697 100C172.697 89.2437 170.36 79.0331 166.168 69.8477C165.71 68.8453 164.235 69.054 163.989 70.128C163.241 73.3838 162.188 76.7352 160.871 80.1347C154.995 95.2954 143.5 112.35 127.925 127.925C121.9 133.95 113.488 137.837 104.999 139.012ZM130.152 166.168C131.155 165.71 130.946 164.235 129.872 163.989C126.616 163.241 123.265 162.188 119.865 160.87C104.705 154.995 87.6501 143.5 72.0752 127.925C67.2172 123.067 62.3556 114.804 60.909 105.103C60.7487 104.028 59.3109 103.652 58.7174 104.563C54.4133 111.166 51.055 117.606 48.7369 123.588C46.1113 130.364 44.9107 136.317 45.0134 141.124C45.1153 145.895 46.4757 149.207 48.6345 151.366C51.0118 153.743 54.8134 155.163 60.4045 154.977C65.6245 154.802 71.9888 153.219 79.1074 150.161C80.0901 149.739 81.2281 149.874 82.0681 150.536L87.2306 154.604C88.1576 155.335 88.0866 156.759 87.0628 157.346C81.4234 160.58 75.1058 162.779 70.1222 163.969C69.0505 164.225 68.8452 165.71 69.8477 166.168C79.033 170.36 89.2437 172.697 100 172.697C110.756 172.697 120.967 170.36 130.152 166.168Z"
fill="white"
/>
<path
d="M96.8811 80.4348C96.9279 80.3934 96.9716 80.3529 97.0177 80.3108C98.8427 78.6433 101.651 78.6734 103.44 80.3821C103.478 80.4186 103.514 80.4531 103.553 80.4891C104.165 81.058 109.693 86.2109 112.922 89.7439C116.226 93.7887 116.944 99.7632 114.716 104.672C108.586 118.18 91.6728 118.309 85.3654 104.906C83.0057 99.8927 83.755 93.7116 87.2225 89.6184C89.839 86.6809 96.1575 81.0748 96.8811 80.4348Z"
fill="white"
/>
</g>
<defs>
<radialGradient
id="paint0_radial_1016_8"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(200 -4.5) rotate(134.363) scale(286.042 191.372)"
>
<stop stop-color="#5278FF" />
<stop offset="0.361196" stop-color="#BE7CFF" />
<stop offset="0.756992" stop-color="#E69EFE" />
<stop offset="1" stop-color="#F7D4F5" />
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,16 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 24 24">
<path
fill="#FFF"
d="M23.6,14.9C22,21.3,15.5,25.2,9,23.6C2.6,22-1.3,15.5,0.3,9.1S8.4-1.2,14.8,0.4C21.3,2,25.2,8.5,23.6,14.9
L23.6,14.9L23.6,14.9z"
/>
<path
fill="#000"
d="M12,15.1c-1.7,0-3.1-1.4-3.1-3.1c0-0.6,0.2-1.2,0.5-1.6L5.4,7.1v11.5h11.5l-3.3-3.9C13.1,15,12.5,15.1,12,15.1z
"
/>
<path
fill="#000"
d="M6.4,5.4l3.5,4.2c0.5-0.5,1.3-0.8,2-0.8c1.7,0,3.1,1.4,3.1,3.1c0,0.7-0.3,1.4-0.7,2l4.1,3.6V5.4H6.4z"
/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

View File

@ -1,340 +1,340 @@
<svg version="1.1" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg fill='none' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
d="M22.8084 4.42014C22.5667 3.50503 21.7897 2.58992 20.3912 1.57122C19.2689 0.759705 18.0775 0.293518 17.1279 0.293518C16.9379 0.293518 16.7653 0.310784 16.5926 0.345317C16.1609 0.431648 15.7811 0.742439 15.5394 1.20863C15.2458 1.76114 15.1768 2.50359 15.3667 2.95251C15.4358 3.09064 15.5221 3.2633 15.6257 3.4187C14.7106 3.97122 14.1926 4.12661 14.1235 4.14388C16.5063 4.93812 18.4919 6.59568 19.735 8.75395L19.7523 8.54676C19.8041 7.97697 19.9768 7.32086 20.2185 6.64748C20.4602 6.71654 20.702 6.75107 20.9437 6.75107C21.5825 6.75107 22.135 6.49208 22.4804 6.02589C22.8257 5.55971 22.9638 4.93812 22.8084 4.42014Z"
fill="#5E12A0"
d='M22.8084 4.42014C22.5667 3.50503 21.7897 2.58992 20.3912 1.57122C19.2689 0.759705 18.0775 0.293518 17.1279 0.293518C16.9379 0.293518 16.7653 0.310784 16.5926 0.345317C16.1609 0.431648 15.7811 0.742439 15.5394 1.20863C15.2458 1.76114 15.1768 2.50359 15.3667 2.95251C15.4358 3.09064 15.5221 3.2633 15.6257 3.4187C14.7106 3.97122 14.1926 4.12661 14.1235 4.14388C16.5063 4.93812 18.4919 6.59568 19.735 8.75395L19.7523 8.54676C19.8041 7.97697 19.9768 7.32086 20.2185 6.64748C20.4602 6.71654 20.702 6.75107 20.9437 6.75107C21.5825 6.75107 22.135 6.49208 22.4804 6.02589C22.8257 5.55971 22.9638 4.93812 22.8084 4.42014Z'
fill='#5E12A0'
></path>
<path
d="M20.3225 6.14679C21.8937 6.57844 22.5326 5.36981 22.3254 4.5583C22.1009 3.74679 21.3757 2.91801 20.098 1.98564C18.8203 1.05326 17.5254 0.673409 16.6966 0.846071C15.8678 1.01873 15.6261 2.27916 15.8333 2.76262C15.9196 2.95255 16.1096 3.2288 16.3685 3.5396C16.0405 3.76406 15.7297 3.93672 15.4707 4.09211C17.0592 4.80003 18.4405 5.90506 19.4765 7.28636C19.5973 6.82017 19.77 6.40578 19.9254 6.04319C20.0462 6.06046 20.1844 6.09499 20.3225 6.14679Z"
fill="url(#paint0_radial_8766_48743)"
d='M20.3225 6.14679C21.8937 6.57844 22.5326 5.36981 22.3254 4.5583C22.1009 3.74679 21.3757 2.91801 20.098 1.98564C18.8203 1.05326 17.5254 0.673409 16.6966 0.846071C15.8678 1.01873 15.6261 2.27916 15.8333 2.76262C15.9196 2.95255 16.1096 3.2288 16.3685 3.5396C16.0405 3.76406 15.7297 3.93672 15.4707 4.09211C17.0592 4.80003 18.4405 5.90506 19.4765 7.28636C19.5973 6.82017 19.77 6.40578 19.9254 6.04319C20.0462 6.06046 20.1844 6.09499 20.3225 6.14679Z'
fill='url(#paint0_radial_8766_48743)'
></path>
<path
d="M11.3266 23.0676C16.6572 23.0676 20.9784 18.7464 20.9784 13.4158C20.9784 8.08525 16.6572 3.76404 11.3266 3.76404C5.99601 3.76404 1.6748 8.08525 1.6748 13.4158C1.6748 18.7464 5.99601 23.0676 11.3266 23.0676Z"
fill="url(#paint1_radial_8766_48743)"
d='M11.3266 23.0676C16.6572 23.0676 20.9784 18.7464 20.9784 13.4158C20.9784 8.08525 16.6572 3.76404 11.3266 3.76404C5.99601 3.76404 1.6748 8.08525 1.6748 13.4158C1.6748 18.7464 5.99601 23.0676 11.3266 23.0676Z'
fill='url(#paint1_radial_8766_48743)'
></path>
<path
d="M21.5307 3.76403C20.2185 2.38274 19.1134 2.02015 17.784 1.72662C16.748 1.4849 17.0242 0.897845 18.2847 1.01871C17.6804 0.811514 17.1106 0.759716 16.6962 0.846047C15.8674 1.01871 15.6257 2.27914 15.8329 2.76259C15.9192 2.95252 16.1091 3.22878 16.3681 3.53957C15.9019 3.85036 15.5048 4.07482 15.1768 4.24749C15.3322 4.31655 15.5221 4.40288 15.7465 4.52375C16.3336 4.83454 16.9724 5.35252 16.9724 5.35252C16.0055 4.52375 16.2127 4.14389 17.5422 3.21151C17.9566 2.91799 18.7163 2.95252 19.4242 3.31511C20.1322 3.6777 20.9609 4.59281 20.9609 4.59281L20.1667 6.11223C20.2185 6.1295 20.2703 6.14677 20.3221 6.16403C20.8228 6.30216 21.2199 6.26763 21.5307 6.14677C21.8933 5.92231 22.8429 5.16259 21.5307 3.76403Z"
fill="#A98698"
fill-opacity="0.6"
d='M21.5307 3.76403C20.2185 2.38274 19.1134 2.02015 17.784 1.72662C16.748 1.4849 17.0242 0.897845 18.2847 1.01871C17.6804 0.811514 17.1106 0.759716 16.6962 0.846047C15.8674 1.01871 15.6257 2.27914 15.8329 2.76259C15.9192 2.95252 16.1091 3.22878 16.3681 3.53957C15.9019 3.85036 15.5048 4.07482 15.1768 4.24749C15.3322 4.31655 15.5221 4.40288 15.7465 4.52375C16.3336 4.83454 16.9724 5.35252 16.9724 5.35252C16.0055 4.52375 16.2127 4.14389 17.5422 3.21151C17.9566 2.91799 18.7163 2.95252 19.4242 3.31511C20.1322 3.6777 20.9609 4.59281 20.9609 4.59281L20.1667 6.11223C20.2185 6.1295 20.2703 6.14677 20.3221 6.16403C20.8228 6.30216 21.2199 6.26763 21.5307 6.14677C21.8933 5.92231 22.8429 5.16259 21.5307 3.76403Z'
fill='#A98698'
fill-opacity='0.6'
></path>
<path
d="M17.7671 2.55539C18.1124 2.69352 18.5613 2.93525 19.1138 3.29784C19.7699 3.72949 20.3397 4.21295 20.7023 4.5928C20.098 5.38705 19.7009 6.47482 19.4592 7.23453C19.58 7.40719 19.7181 7.57985 19.839 7.75252C19.9599 7.32086 20.1671 6.68201 20.4433 6.04316C20.5124 6.06043 20.5987 6.06043 20.6851 6.06043C20.8922 6.06043 21.134 6.0259 21.3239 5.8705C21.462 5.7669 21.6174 5.57698 21.6002 5.23165C21.6002 4.90359 21.3412 4.48921 20.8059 3.98849C20.4261 3.6259 19.9081 3.22878 19.3901 2.86619C17.9052 1.88201 16.8692 1.60575 16.403 2.07194C16.0922 2.38273 16.1268 2.76259 16.2304 3.03885C15.6779 3.40144 15.2117 3.66043 14.9009 3.83309C15.1081 3.90216 15.298 3.98849 15.5052 4.07482C16.0577 3.78129 16.852 3.28057 17.7671 2.55539ZM21.0304 5.02446C21.0822 5.11079 21.0994 5.19712 21.0994 5.24892C21.0994 5.40431 21.0476 5.45611 21.0131 5.49065C20.944 5.54244 20.8059 5.57698 20.6851 5.57698C20.7886 5.36978 20.9095 5.19712 21.0304 5.02446ZM16.7656 2.4518C16.8174 2.4 16.9556 2.36547 17.18 2.4C17.0074 2.53813 16.8347 2.65899 16.662 2.77985C16.6448 2.65899 16.662 2.53813 16.7656 2.4518Z"
fill="#5E12A0"
d='M17.7671 2.55539C18.1124 2.69352 18.5613 2.93525 19.1138 3.29784C19.7699 3.72949 20.3397 4.21295 20.7023 4.5928C20.098 5.38705 19.7009 6.47482 19.4592 7.23453C19.58 7.40719 19.7181 7.57985 19.839 7.75252C19.9599 7.32086 20.1671 6.68201 20.4433 6.04316C20.5124 6.06043 20.5987 6.06043 20.6851 6.06043C20.8922 6.06043 21.134 6.0259 21.3239 5.8705C21.462 5.7669 21.6174 5.57698 21.6002 5.23165C21.6002 4.90359 21.3412 4.48921 20.8059 3.98849C20.4261 3.6259 19.9081 3.22878 19.3901 2.86619C17.9052 1.88201 16.8692 1.60575 16.403 2.07194C16.0922 2.38273 16.1268 2.76259 16.2304 3.03885C15.6779 3.40144 15.2117 3.66043 14.9009 3.83309C15.1081 3.90216 15.298 3.98849 15.5052 4.07482C16.0577 3.78129 16.852 3.28057 17.7671 2.55539ZM21.0304 5.02446C21.0822 5.11079 21.0994 5.19712 21.0994 5.24892C21.0994 5.40431 21.0476 5.45611 21.0131 5.49065C20.944 5.54244 20.8059 5.57698 20.6851 5.57698C20.7886 5.36978 20.9095 5.19712 21.0304 5.02446ZM16.7656 2.4518C16.8174 2.4 16.9556 2.36547 17.18 2.4C17.0074 2.53813 16.8347 2.65899 16.662 2.77985C16.6448 2.65899 16.662 2.53813 16.7656 2.4518Z'
fill='#5E12A0'
></path>
<path
d="M11.3266 3.19427C5.68052 3.19427 1.10498 7.76981 1.10498 13.4159C1.10498 19.0619 5.68052 23.6374 11.3266 23.6374C16.9726 23.6374 21.5481 19.0619 21.5481 13.4159C21.5481 7.76981 16.9553 3.19427 11.3266 3.19427ZM11.3266 23.0677C5.99131 23.0677 1.67476 18.7511 1.67476 13.4159C1.67476 8.08061 5.99131 3.76406 11.3266 3.76406C16.6618 3.76406 20.9784 8.08061 20.9784 13.4159C20.9784 18.7511 16.6445 23.0677 11.3266 23.0677Z"
fill="#5E12A0"
d='M11.3266 3.19427C5.68052 3.19427 1.10498 7.76981 1.10498 13.4159C1.10498 19.0619 5.68052 23.6374 11.3266 23.6374C16.9726 23.6374 21.5481 19.0619 21.5481 13.4159C21.5481 7.76981 16.9553 3.19427 11.3266 3.19427ZM11.3266 23.0677C5.99131 23.0677 1.67476 18.7511 1.67476 13.4159C1.67476 8.08061 5.99131 3.76406 11.3266 3.76406C16.6618 3.76406 20.9784 8.08061 20.9784 13.4159C20.9784 18.7511 16.6445 23.0677 11.3266 23.0677Z'
fill='#5E12A0'
></path>
<path
d="M11.3266 23.0676C16.6572 23.0676 20.9784 18.7464 20.9784 13.4158C20.9784 8.08525 16.6572 3.76404 11.3266 3.76404C5.99601 3.76404 1.6748 8.08525 1.6748 13.4158C1.6748 18.7464 5.99601 23.0676 11.3266 23.0676Z"
fill="url(#paint2_linear_8766_48743)"
d='M11.3266 23.0676C16.6572 23.0676 20.9784 18.7464 20.9784 13.4158C20.9784 8.08525 16.6572 3.76404 11.3266 3.76404C5.99601 3.76404 1.6748 8.08525 1.6748 13.4158C1.6748 18.7464 5.99601 23.0676 11.3266 23.0676Z'
fill='url(#paint2_linear_8766_48743)'
></path>
<path
d="M11.2577 21.8935C5.99153 21.0475 2.41743 16.0921 3.28074 10.8259C3.6606 8.49494 4.8347 6.50933 6.49225 5.07623C4.07499 6.47479 2.2793 8.90933 1.79585 11.8791C0.949806 17.1453 4.52391 22.1007 9.77283 22.9467C12.7081 23.4302 15.5397 22.5151 17.6289 20.7194C15.7815 21.7899 13.5369 22.2561 11.2577 21.8935Z"
fill="#A98698"
fill-opacity="0.6"
d='M11.2577 21.8935C5.99153 21.0475 2.41743 16.0921 3.28074 10.8259C3.6606 8.49494 4.8347 6.50933 6.49225 5.07623C4.07499 6.47479 2.2793 8.90933 1.79585 11.8791C0.949806 17.1453 4.52391 22.1007 9.77283 22.9467C12.7081 23.4302 15.5397 22.5151 17.6289 20.7194C15.7815 21.7899 13.5369 22.2561 11.2577 21.8935Z'
fill='#A98698'
fill-opacity='0.6'
></path>
<path
d="M12.8631 3.90216C10.4285 3.50504 8.06307 4.05756 6.12926 5.28346C6.09473 5.31799 6.0602 5.35252 6.0602 5.35252C6.80264 4.9036 7.89041 4.50648 7.89041 4.50648C5.09329 6.1295 4.22998 7.97698 4.22998 7.97698C5.31775 5.87051 8.51199 4.38562 11.0156 4.28202C13.5192 4.17842 15.1595 4.92087 17.1624 6.52662C19.1652 8.14964 20.3739 11.4648 20.253 14.0892C20.1494 16.7137 18.7681 18.8374 18.7681 18.8374C19.7177 17.6115 20.2875 16.7137 20.6501 15.7986C20.7192 15.5223 20.7883 15.246 20.8228 14.9525C21.6861 9.7036 18.1293 4.74821 12.8631 3.90216Z"
fill="url(#paint3_linear_8766_48743)"
d='M12.8631 3.90216C10.4285 3.50504 8.06307 4.05756 6.12926 5.28346C6.09473 5.31799 6.0602 5.35252 6.0602 5.35252C6.80264 4.9036 7.89041 4.50648 7.89041 4.50648C5.09329 6.1295 4.22998 7.97698 4.22998 7.97698C5.31775 5.87051 8.51199 4.38562 11.0156 4.28202C13.5192 4.17842 15.1595 4.92087 17.1624 6.52662C19.1652 8.14964 20.3739 11.4648 20.253 14.0892C20.1494 16.7137 18.7681 18.8374 18.7681 18.8374C19.7177 17.6115 20.2875 16.7137 20.6501 15.7986C20.7192 15.5223 20.7883 15.246 20.8228 14.9525C21.6861 9.7036 18.1293 4.74821 12.8631 3.90216Z'
fill='url(#paint3_linear_8766_48743)'
></path>
<path
d="M20.4951 13.3295C20.4951 18.3885 16.3857 22.4978 11.3267 22.4978C6.26773 22.4978 2.14111 18.3885 2.14111 13.3295H20.4951Z"
fill="url(#paint4_linear_8766_48743)"
d='M20.4951 13.3295C20.4951 18.3885 16.3857 22.4978 11.3267 22.4978C6.26773 22.4978 2.14111 18.3885 2.14111 13.3295H20.4951Z'
fill='url(#paint4_linear_8766_48743)'
></path>
<path
d="M19.7696 13.3295C19.7696 18.2676 15.8675 22.3079 10.9639 22.4978H11.3265C16.3855 22.4978 20.4948 18.3885 20.4948 13.3295H19.7696Z"
fill="url(#paint5_linear_8766_48743)"
d='M19.7696 13.3295C19.7696 18.2676 15.8675 22.3079 10.9639 22.4978H11.3265C16.3855 22.4978 20.4948 18.3885 20.4948 13.3295H19.7696Z'
fill='url(#paint5_linear_8766_48743)'
></path>
<path
d="M3.43608 13.3295H2.14111C2.14111 18.3885 6.25047 22.4978 11.3095 22.4978C11.5339 22.4978 11.7411 22.4978 11.9483 22.4805C7.20011 22.1352 3.43608 18.164 3.43608 13.3295Z"
fill="url(#paint6_linear_8766_48743)"
d='M3.43608 13.3295H2.14111C2.14111 18.3885 6.25047 22.4978 11.3095 22.4978C11.5339 22.4978 11.7411 22.4978 11.9483 22.4805C7.20011 22.1352 3.43608 18.164 3.43608 13.3295Z'
fill='url(#paint6_linear_8766_48743)'
></path>
<path
d="M20.4778 13.4158C20.4778 12.3626 18.6476 11.7583 16.2131 11.5511C14.4519 11.413 12.6735 11.5856 10.6361 12.2072C8.87493 12.7252 7.28644 12.6389 6.1296 12.5007C3.55694 12.2072 2.14111 12.1727 2.14111 13.4158C2.14111 15.2115 5.80155 17.4561 11.2922 16.6792C14.072 16.282 15.5051 15.4705 17.1454 14.918C18.9238 14.331 20.4778 14.3482 20.4778 13.4158Z"
fill="url(#paint7_linear_8766_48743)"
d='M20.4778 13.4158C20.4778 12.3626 18.6476 11.7583 16.2131 11.5511C14.4519 11.413 12.6735 11.5856 10.6361 12.2072C8.87493 12.7252 7.28644 12.6389 6.1296 12.5007C3.55694 12.2072 2.14111 12.1727 2.14111 13.4158C2.14111 15.2115 5.80155 17.4561 11.2922 16.6792C14.072 16.282 15.5051 15.4705 17.1454 14.918C18.9238 14.331 20.4778 14.3482 20.4778 13.4158Z'
fill='url(#paint7_linear_8766_48743)'
></path>
<path
d="M14.3308 9.06476C15.1891 9.06476 15.8848 8.36911 15.8848 7.5108C15.8848 6.6525 15.1891 5.95685 14.3308 5.95685C13.4725 5.95685 12.7769 6.6525 12.7769 7.5108C12.7769 8.36911 13.4725 9.06476 14.3308 9.06476Z"
fill="white"
d='M14.3308 9.06476C15.1891 9.06476 15.8848 8.36911 15.8848 7.5108C15.8848 6.6525 15.1891 5.95685 14.3308 5.95685C13.4725 5.95685 12.7769 6.6525 12.7769 7.5108C12.7769 8.36911 13.4725 9.06476 14.3308 9.06476Z'
fill='white'
></path>
<path
d="M16.869 10.2216C17.2314 10.2216 17.5251 9.9279 17.5251 9.56548C17.5251 9.20306 17.2314 8.90936 16.869 8.90936C16.5066 8.90936 16.2129 9.20306 16.2129 9.56548C16.2129 9.9279 16.5066 10.2216 16.869 10.2216Z"
fill="white"
d='M16.869 10.2216C17.2314 10.2216 17.5251 9.9279 17.5251 9.56548C17.5251 9.20306 17.2314 8.90936 16.869 8.90936C16.5066 8.90936 16.2129 9.20306 16.2129 9.56548C16.2129 9.9279 16.5066 10.2216 16.869 10.2216Z'
fill='white'
></path>
<path
d="M19.2175 6.2676H19.1829C19.0793 6.25034 19.0103 6.14674 19.0275 6.02588C19.1829 5.23163 19.8391 4.47192 19.8736 4.43739C19.9427 4.35106 20.0808 4.35106 20.1498 4.42012C20.2362 4.48919 20.2362 4.62732 20.1671 4.69638C20.1498 4.71365 19.5455 5.42156 19.4074 6.11221C19.3901 6.2158 19.3038 6.2676 19.2175 6.2676Z"
fill="url(#paint8_linear_8766_48743)"
d='M19.2175 6.2676H19.1829C19.0793 6.25034 19.0103 6.14674 19.0275 6.02588C19.1829 5.23163 19.8391 4.47192 19.8736 4.43739C19.9427 4.35106 20.0808 4.35106 20.1498 4.42012C20.2362 4.48919 20.2362 4.62732 20.1671 4.69638C20.1498 4.71365 19.5455 5.42156 19.4074 6.11221C19.3901 6.2158 19.3038 6.2676 19.2175 6.2676Z'
fill='url(#paint8_linear_8766_48743)'
></path>
<path
d="M10.6876 20.6158C10.9545 20.6158 11.171 20.3993 11.171 20.1324C11.171 19.8654 10.9545 19.6489 10.6876 19.6489C10.4206 19.6489 10.2041 19.8654 10.2041 20.1324C10.2041 20.3993 10.4206 20.6158 10.6876 20.6158Z"
fill="white"
fill-opacity="0.2"
d='M10.6876 20.6158C10.9545 20.6158 11.171 20.3993 11.171 20.1324C11.171 19.8654 10.9545 19.6489 10.6876 19.6489C10.4206 19.6489 10.2041 19.8654 10.2041 20.1324C10.2041 20.3993 10.4206 20.6158 10.6876 20.6158Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M10.4117 20.4432C10.2218 20.2532 10.2218 19.9425 10.4117 19.7525C10.4462 19.718 10.4808 19.7007 10.5153 19.6662C10.4462 19.6835 10.3944 19.718 10.3426 19.7698C10.1527 19.9597 10.1527 20.2705 10.3426 20.4604C10.498 20.6158 10.7398 20.6504 10.9297 20.5468C10.757 20.6158 10.5498 20.5813 10.4117 20.4432Z"
fill="url(#paint9_linear_8766_48743)"
d='M10.4117 20.4432C10.2218 20.2532 10.2218 19.9425 10.4117 19.7525C10.4462 19.718 10.4808 19.7007 10.5153 19.6662C10.4462 19.6835 10.3944 19.718 10.3426 19.7698C10.1527 19.9597 10.1527 20.2705 10.3426 20.4604C10.498 20.6158 10.7398 20.6504 10.9297 20.5468C10.757 20.6158 10.5498 20.5813 10.4117 20.4432Z'
fill='url(#paint9_linear_8766_48743)'
></path>
<path
d="M10.8604 19.9942C10.9176 19.9942 10.964 19.9478 10.964 19.8906C10.964 19.8335 10.9176 19.787 10.8604 19.787C10.8033 19.787 10.7568 19.8335 10.7568 19.8906C10.7568 19.9478 10.8033 19.9942 10.8604 19.9942Z"
fill="white"
fill-opacity="0.3"
d='M10.8604 19.9942C10.9176 19.9942 10.964 19.9478 10.964 19.8906C10.964 19.8335 10.9176 19.787 10.8604 19.787C10.8033 19.787 10.7568 19.8335 10.7568 19.8906C10.7568 19.9478 10.8033 19.9942 10.8604 19.9942Z'
fill='white'
fill-opacity='0.3'
></path>
<path
d="M13.2086 20.3741C13.4755 20.3741 13.692 20.1576 13.692 19.8906C13.692 19.6237 13.4755 19.4072 13.2086 19.4072C12.9416 19.4072 12.7251 19.6237 12.7251 19.8906C12.7251 20.1576 12.9416 20.3741 13.2086 20.3741Z"
fill="white"
fill-opacity="0.2"
d='M13.2086 20.3741C13.4755 20.3741 13.692 20.1576 13.692 19.8906C13.692 19.6237 13.4755 19.4072 13.2086 19.4072C12.9416 19.4072 12.7251 19.6237 12.7251 19.8906C12.7251 20.1576 12.9416 20.3741 13.2086 20.3741Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M12.9322 20.2014C12.7423 20.0115 12.7423 19.7007 12.9322 19.5108C12.9667 19.4762 13.0013 19.459 13.0358 19.4244C12.9667 19.4417 12.9149 19.4762 12.8631 19.528C12.6732 19.718 12.6732 20.0288 12.8631 20.2187C13.0185 20.3741 13.2603 20.4086 13.4502 20.305C13.2775 20.3741 13.0703 20.3395 12.9322 20.2014Z"
fill="url(#paint10_linear_8766_48743)"
d='M12.9322 20.2014C12.7423 20.0115 12.7423 19.7007 12.9322 19.5108C12.9667 19.4762 13.0013 19.459 13.0358 19.4244C12.9667 19.4417 12.9149 19.4762 12.8631 19.528C12.6732 19.718 12.6732 20.0288 12.8631 20.2187C13.0185 20.3741 13.2603 20.4086 13.4502 20.305C13.2775 20.3741 13.0703 20.3395 12.9322 20.2014Z'
fill='url(#paint10_linear_8766_48743)'
></path>
<path
d="M13.3814 19.7525C13.4386 19.7525 13.485 19.7061 13.485 19.6489C13.485 19.5918 13.4386 19.5453 13.3814 19.5453C13.3243 19.5453 13.2778 19.5918 13.2778 19.6489C13.2778 19.7061 13.3243 19.7525 13.3814 19.7525Z"
fill="white"
fill-opacity="0.3"
d='M13.3814 19.7525C13.4386 19.7525 13.485 19.7061 13.485 19.6489C13.485 19.5918 13.4386 19.5453 13.3814 19.5453C13.3243 19.5453 13.2778 19.5918 13.2778 19.6489C13.2778 19.7061 13.3243 19.7525 13.3814 19.7525Z'
fill='white'
fill-opacity='0.3'
></path>
<path
d="M11.9656 21.4964C12.1659 21.4964 12.3282 21.3341 12.3282 21.1338C12.3282 20.9335 12.1659 20.7712 11.9656 20.7712C11.7653 20.7712 11.603 20.9335 11.603 21.1338C11.603 21.3341 11.7653 21.4964 11.9656 21.4964Z"
fill="white"
fill-opacity="0.2"
d='M11.9656 21.4964C12.1659 21.4964 12.3282 21.3341 12.3282 21.1338C12.3282 20.9335 12.1659 20.7712 11.9656 20.7712C11.7653 20.7712 11.603 20.9335 11.603 21.1338C11.603 21.3341 11.7653 21.4964 11.9656 21.4964Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M11.7584 21.3583C11.6203 21.2202 11.6203 20.9784 11.7584 20.8403C11.7757 20.823 11.8102 20.8058 11.8275 20.7885C11.7757 20.8058 11.7412 20.8403 11.7066 20.8748C11.5685 21.013 11.5685 21.2547 11.7066 21.3928C11.8275 21.5137 12.0002 21.531 12.1555 21.4446C12.0174 21.4964 11.862 21.4619 11.7584 21.3583Z"
fill="url(#paint11_linear_8766_48743)"
d='M11.7584 21.3583C11.6203 21.2202 11.6203 20.9784 11.7584 20.8403C11.7757 20.823 11.8102 20.8058 11.8275 20.7885C11.7757 20.8058 11.7412 20.8403 11.7066 20.8748C11.5685 21.013 11.5685 21.2547 11.7066 21.3928C11.8275 21.5137 12.0002 21.531 12.1555 21.4446C12.0174 21.4964 11.862 21.4619 11.7584 21.3583Z'
fill='url(#paint11_linear_8766_48743)'
></path>
<path
d="M12.0862 21.0129C12.1243 21.0129 12.1552 20.982 12.1552 20.9439C12.1552 20.9057 12.1243 20.8748 12.0862 20.8748C12.048 20.8748 12.0171 20.9057 12.0171 20.9439C12.0171 20.982 12.048 21.0129 12.0862 21.0129Z"
fill="white"
fill-opacity="0.3"
d='M12.0862 21.0129C12.1243 21.0129 12.1552 20.982 12.1552 20.9439C12.1552 20.9057 12.1243 20.8748 12.0862 20.8748C12.048 20.8748 12.0171 20.9057 12.0171 20.9439C12.0171 20.982 12.048 21.0129 12.0862 21.0129Z'
fill='white'
fill-opacity='0.3'
></path>
<path
d="M14.1927 21.2374C14.393 21.2374 14.5553 21.0751 14.5553 20.8748C14.5553 20.6745 14.393 20.5122 14.1927 20.5122C13.9924 20.5122 13.8301 20.6745 13.8301 20.8748C13.8301 21.0751 13.9924 21.2374 14.1927 21.2374Z"
fill="white"
fill-opacity="0.2"
d='M14.1927 21.2374C14.393 21.2374 14.5553 21.0751 14.5553 20.8748C14.5553 20.6745 14.393 20.5122 14.1927 20.5122C13.9924 20.5122 13.8301 20.6745 13.8301 20.8748C13.8301 21.0751 13.9924 21.2374 14.1927 21.2374Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M13.9855 21.0993C13.8473 20.9611 13.8473 20.7194 13.9855 20.5813C14.0027 20.564 14.0373 20.5467 14.0545 20.5295C14.0027 20.5467 13.9682 20.5813 13.9337 20.6158C13.7955 20.7539 13.7955 20.9957 13.9337 21.1338C14.0545 21.2547 14.2272 21.2719 14.3826 21.1856C14.2445 21.2374 14.0891 21.2029 13.9855 21.0993Z"
fill="url(#paint12_linear_8766_48743)"
d='M13.9855 21.0993C13.8473 20.9611 13.8473 20.7194 13.9855 20.5813C14.0027 20.564 14.0373 20.5467 14.0545 20.5295C14.0027 20.5467 13.9682 20.5813 13.9337 20.6158C13.7955 20.7539 13.7955 20.9957 13.9337 21.1338C14.0545 21.2547 14.2272 21.2719 14.3826 21.1856C14.2445 21.2374 14.0891 21.2029 13.9855 21.0993Z'
fill='url(#paint12_linear_8766_48743)'
></path>
<path
d="M14.3137 20.754C14.3519 20.754 14.3828 20.7231 14.3828 20.6849C14.3828 20.6468 14.3519 20.6158 14.3137 20.6158C14.2755 20.6158 14.2446 20.6468 14.2446 20.6849C14.2446 20.7231 14.2755 20.754 14.3137 20.754Z"
fill="white"
fill-opacity="0.3"
d='M14.3137 20.754C14.3519 20.754 14.3828 20.7231 14.3828 20.6849C14.3828 20.6468 14.3519 20.6158 14.3137 20.6158C14.2755 20.6158 14.2446 20.6468 14.2446 20.6849C14.2446 20.7231 14.2755 20.754 14.3137 20.754Z'
fill='white'
fill-opacity='0.3'
></path>
<path
d="M8.63311 20.4432C9.07185 20.4432 9.42736 20.0877 9.42736 19.6489C9.42736 19.2104 9.07185 18.8547 8.63311 18.8547C8.19455 18.8547 7.83887 19.2104 7.83887 19.6489C7.83887 20.0877 8.19455 20.4432 8.63311 20.4432Z"
fill="white"
fill-opacity="0.2"
d='M8.63311 20.4432C9.07185 20.4432 9.42736 20.0877 9.42736 19.6489C9.42736 19.2104 9.07185 18.8547 8.63311 18.8547C8.19455 18.8547 7.83887 19.2104 7.83887 19.6489C7.83887 20.0877 8.19455 20.4432 8.63311 20.4432Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M8.18435 20.1497C7.87356 19.8389 7.87356 19.3381 8.18435 19.0274C8.23615 18.9756 8.28794 18.941 8.35701 18.9065C8.25341 18.941 8.16708 19.0101 8.08075 19.0792C7.76996 19.3899 7.76996 19.8907 8.08075 20.2015C8.33974 20.4605 8.73687 20.5122 9.04766 20.3223C8.75413 20.4432 8.40881 20.3914 8.18435 20.1497Z"
fill="url(#paint13_linear_8766_48743)"
d='M8.18435 20.1497C7.87356 19.8389 7.87356 19.3381 8.18435 19.0274C8.23615 18.9756 8.28794 18.941 8.35701 18.9065C8.25341 18.941 8.16708 19.0101 8.08075 19.0792C7.76996 19.3899 7.76996 19.8907 8.08075 20.2015C8.33974 20.4605 8.73687 20.5122 9.04766 20.3223C8.75413 20.4432 8.40881 20.3914 8.18435 20.1497Z'
fill='url(#paint13_linear_8766_48743)'
></path>
<path
d="M8.90948 19.4072C9.00479 19.4072 9.08214 19.3299 9.08214 19.2346C9.08214 19.1392 9.00479 19.0619 8.90948 19.0619C8.81417 19.0619 8.73682 19.1392 8.73682 19.2346C8.73682 19.3299 8.81417 19.4072 8.90948 19.4072Z"
fill="white"
fill-opacity="0.3"
d='M8.90948 19.4072C9.00479 19.4072 9.08214 19.3299 9.08214 19.2346C9.08214 19.1392 9.00479 19.0619 8.90948 19.0619C8.81417 19.0619 8.73682 19.1392 8.73682 19.2346C8.73682 19.3299 8.81417 19.4072 8.90948 19.4072Z'
fill='white'
fill-opacity='0.3'
></path>
<path
d="M15.7293 18.4921C16.168 18.4921 16.5235 18.1366 16.5235 17.6979C16.5235 17.2593 16.168 16.9036 15.7293 16.9036C15.2907 16.9036 14.9351 17.2593 14.9351 17.6979C14.9351 18.1366 15.2907 18.4921 15.7293 18.4921Z"
fill="white"
fill-opacity="0.2"
d='M15.7293 18.4921C16.168 18.4921 16.5235 18.1366 16.5235 17.6979C16.5235 17.2593 16.168 16.9036 15.7293 16.9036C15.2907 16.9036 14.9351 17.2593 14.9351 17.6979C14.9351 18.1366 15.2907 18.4921 15.7293 18.4921Z'
fill='white'
fill-opacity='0.2'
></path>
<path
d="M15.2634 18.1985C14.9527 17.8878 14.9527 17.387 15.2634 17.0762C15.3152 17.0244 15.367 16.9899 15.4361 16.9554C15.3325 16.9899 15.2462 17.059 15.1599 17.128C14.8491 17.4388 14.8491 17.9396 15.1599 18.2503C15.4188 18.5093 15.816 18.5611 16.1268 18.3712C15.8332 18.4921 15.5052 18.4403 15.2634 18.1985Z"
fill="url(#paint14_linear_8766_48743)"
d='M15.2634 18.1985C14.9527 17.8878 14.9527 17.387 15.2634 17.0762C15.3152 17.0244 15.367 16.9899 15.4361 16.9554C15.3325 16.9899 15.2462 17.059 15.1599 17.128C14.8491 17.4388 14.8491 17.9396 15.1599 18.2503C15.4188 18.5093 15.816 18.5611 16.1268 18.3712C15.8332 18.4921 15.5052 18.4403 15.2634 18.1985Z'
fill='url(#paint14_linear_8766_48743)'
></path>
<path
d="M16.0057 17.4561C16.101 17.4561 16.1783 17.3788 16.1783 17.2834C16.1783 17.1881 16.101 17.1108 16.0057 17.1108C15.9104 17.1108 15.833 17.1881 15.833 17.2834C15.833 17.3788 15.9104 17.4561 16.0057 17.4561Z"
fill="white"
fill-opacity="0.3"
d='M16.0057 17.4561C16.101 17.4561 16.1783 17.3788 16.1783 17.2834C16.1783 17.1881 16.101 17.1108 16.0057 17.1108C15.9104 17.1108 15.833 17.1881 15.833 17.2834C15.833 17.3788 15.9104 17.4561 16.0057 17.4561Z'
fill='white'
fill-opacity='0.3'
></path>
<defs>
<radialGradient
cx="0"
cy="0"
gradientTransform="translate(22.0104 3.47051) scale(7.71702 7.71702)"
gradientUnits="userSpaceOnUse"
id="paint0_radial_8766_48743"
r="1"
cx='0'
cy='0'
gradientTransform='translate(22.0104 3.47051) scale(7.71702 7.71702)'
gradientUnits='userSpaceOnUse'
id='paint0_radial_8766_48743'
r='1'
>
<stop stop-color="#FFEAFF" stop-opacity="0.6"></stop>
<stop offset="0.68" stop-color="#A087C9"></stop>
<stop offset="1" stop-color="#10002F"></stop>
<stop stop-color='#FFEAFF' stop-opacity='0.6'></stop>
<stop offset='0.68' stop-color='#A087C9'></stop>
<stop offset='1' stop-color='#10002F'></stop>
</radialGradient>
<radialGradient
cx="0"
cy="0"
gradientTransform="translate(17.7169 6.76169) scale(18.8808)"
gradientUnits="userSpaceOnUse"
id="paint1_radial_8766_48743"
r="1"
cx='0'
cy='0'
gradientTransform='translate(17.7169 6.76169) scale(18.8808)'
gradientUnits='userSpaceOnUse'
id='paint1_radial_8766_48743'
r='1'
>
<stop stop-color="#FFEAFF" stop-opacity="0.6"></stop>
<stop offset="0.68" stop-color="#A087C9"></stop>
<stop offset="1" stop-color="#10002F"></stop>
<stop stop-color='#FFEAFF' stop-opacity='0.6'></stop>
<stop offset='0.68' stop-color='#A087C9'></stop>
<stop offset='1' stop-color='#10002F'></stop>
</radialGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint2_linear_8766_48743"
x1="9.77838"
x2="12.8655"
y1="22.9307"
y2="3.8849"
gradientUnits='userSpaceOnUse'
id='paint2_linear_8766_48743'
x1='9.77838'
x2='12.8655'
y1='22.9307'
y2='3.8849'
>
<stop stop-color="#81FFFF" stop-opacity="0.6"></stop>
<stop offset="0.62" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='#81FFFF' stop-opacity='0.6'></stop>
<stop offset='0.62' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint3_linear_8766_48743"
x1="18.1284"
x2="10.1473"
y1="6.861"
y2="14.1839"
gradientUnits='userSpaceOnUse'
id='paint3_linear_8766_48743'
x1='18.1284'
x2='10.1473'
y1='6.861'
y2='14.1839'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint4_linear_8766_48743"
x1="2.14889"
x2="20.4906"
y1="17.9083"
y2="17.9083"
gradientUnits='userSpaceOnUse'
id='paint4_linear_8766_48743'
x1='2.14889'
x2='20.4906'
y1='17.9083'
y2='17.9083'
>
<stop stop-color="#0002E9"></stop>
<stop offset="1" stop-color="#FF00C7"></stop>
<stop stop-color='#0002E9'></stop>
<stop offset='1' stop-color='#FF00C7'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint5_linear_8766_48743"
x1="21.3586"
x2="11.3753"
y1="14.134"
y2="23.5688"
gradientUnits='userSpaceOnUse'
id='paint5_linear_8766_48743'
x1='21.3586'
x2='11.3753'
y1='14.134'
y2='23.5688'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint6_linear_8766_48743"
x1="2.14889"
x2="11.9616"
y1="17.9083"
y2="17.9083"
gradientUnits='userSpaceOnUse'
id='paint6_linear_8766_48743'
x1='2.14889'
x2='11.9616'
y1='17.9083'
y2='17.9083'
>
<stop stop-color="#000292" stop-opacity="0.7"></stop>
<stop offset="1" stop-color="#7D00C7" stop-opacity="0.7"></stop>
<stop stop-color='#000292' stop-opacity='0.7'></stop>
<stop offset='1' stop-color='#7D00C7' stop-opacity='0.7'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint7_linear_8766_48743"
x1="2.1612"
x2="20.4784"
y1="14.1775"
y2="14.1775"
gradientUnits='userSpaceOnUse'
id='paint7_linear_8766_48743'
x1='2.1612'
x2='20.4784'
y1='14.1775'
y2='14.1775'
>
<stop stop-color="#000292"></stop>
<stop offset="1" stop-color="#BE00C7"></stop>
<stop stop-color='#000292'></stop>
<stop offset='1' stop-color='#BE00C7'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint8_linear_8766_48743"
x1="20.1778"
x2="18.8614"
y1="4.3533"
y2="6.49258"
gradientUnits='userSpaceOnUse'
id='paint8_linear_8766_48743'
x1='20.1778'
x2='18.8614'
y1='4.3533'
y2='6.49258'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint9_linear_8766_48743"
x1="10.1997"
x2="10.9302"
y1="20.1472"
y2="20.1472"
gradientUnits='userSpaceOnUse'
id='paint9_linear_8766_48743'
x1='10.1997'
x2='10.9302'
y1='20.1472'
y2='20.1472'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint10_linear_8766_48743"
x1="12.7185"
x2="13.449"
y1="19.9022"
y2="19.9022"
gradientUnits='userSpaceOnUse'
id='paint10_linear_8766_48743'
x1='12.7185'
x2='13.449'
y1='19.9022'
y2='19.9022'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint11_linear_8766_48743"
x1="11.6008"
x2="12.1492"
y1="21.138"
y2="21.138"
gradientUnits='userSpaceOnUse'
id='paint11_linear_8766_48743'
x1='11.6008'
x2='12.1492'
y1='21.138'
y2='21.138'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint12_linear_8766_48743"
x1="13.8204"
x2="14.3688"
y1="20.8783"
y2="20.8783"
gradientUnits='userSpaceOnUse'
id='paint12_linear_8766_48743'
x1='13.8204'
x2='14.3688'
y1='20.8783'
y2='20.8783'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint13_linear_8766_48743"
x1="7.83973"
x2="9.03272"
y1="19.6691"
y2="19.6691"
gradientUnits='userSpaceOnUse'
id='paint13_linear_8766_48743'
x1='7.83973'
x2='9.03272'
y1='19.6691'
y2='19.6691'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="paint14_linear_8766_48743"
x1="14.9254"
x2="16.1184"
y1="17.7175"
y2="17.7175"
gradientUnits='userSpaceOnUse'
id='paint14_linear_8766_48743'
x1='14.9254'
x2='16.1184'
y1='17.7175'
y2='17.7175'
>
<stop stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.29" stop-color="white" stop-opacity="0.6"></stop>
<stop offset="0.78" stop-color="white" stop-opacity="0"></stop>
<stop offset="1" stop-color="white" stop-opacity="0"></stop>
<stop stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.29' stop-color='white' stop-opacity='0.6'></stop>
<stop offset='0.78' stop-color='white' stop-opacity='0'></stop>
<stop offset='1' stop-color='white' stop-opacity='0'></stop>
</linearGradient>
</defs>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,36 +0,0 @@
<svg viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="240" height="240" rx="120" fill="#E50571" />
<path d="M149.107 60.9396L63.3276 181.702H89.6637L175.888 60.9396H149.107Z" fill="white" />
<path
d="M92.4053 60.9396L117.644 96.5239L104.476 115.934L65.5205 60.9396H92.4053Z"
fill="url(#paint0_linear_6269_3439)"
/>
<path
d="M151.664 181.712L123.682 142.354L136.851 123.483L178 181.712H151.664Z"
fill="url(#paint1_linear_6269_3439)"
/>
<defs>
<linearGradient
id="paint0_linear_6269_3439"
x1="86.3698"
y1="68.4878"
x2="121.753"
y2="111.957"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0.75" />
</linearGradient>
<linearGradient
id="paint1_linear_6269_3439"
x1="157.7"
y1="172.547"
x2="114.305"
y2="113.014"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0.68" />
<stop offset="1" stop-color="white" stop-opacity="0.1" />
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,25 +1,9 @@
<svg version="1.1" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask
id="mask0_207_398"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="500"
height="500"
>
<path
d="M250 500C388.071 500 500 388.071 500 250C500 111.929 388.071 0 250 0C111.929 0 0 111.929 0 250C0 388.071 111.929 500 250 500Z"
fill="#D9D9D9"
/>
</mask>
<g mask="url(#mask0_207_398)">
<path d="M500 0H0V500H500V0Z" fill="#E50571" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M355.922 116.063C373.552 128.87 383.346 140.375 386.393 151.879C388.352 158.391 386.611 166.206 382.259 172.067C377.904 177.927 370.941 181.183 362.887 181.183C359.841 181.183 356.57 180.717 353.526 179.849C354.448 177.382 355.467 174.831 356.572 172.28C357.443 172.497 358.533 172.497 359.62 172.497C362.23 172.497 365.278 172.062 367.674 170.109C369.413 168.807 371.374 166.419 371.157 162.077C371.157 157.953 367.891 152.743 361.144 146.448C356.357 141.89 349.826 136.897 343.298 132.339C324.578 119.966 311.52 116.493 305.643 122.354C301.726 126.261 302.161 131.037 303.467 134.51C300.741 136.294 298.183 137.878 295.841 139.276C290.198 130.82 289.907 120.621 294.763 111.505C297.809 105.644 302.598 101.737 308.039 100.651C310.215 100.217 312.391 100 314.787 100C326.757 100 341.774 105.861 355.922 116.063ZM294.241 147.657C301.204 143.967 311.302 137.548 322.837 128.432C327.191 130.168 332.85 133.207 339.815 137.766C348.085 143.192 355.267 149.27 359.839 154.046C352.233 164.014 347.23 177.66 344.183 187.208C360.687 208.796 370.502 235.743 370.502 264.972C370.502 335.952 312.826 393.476 241.657 393.476C170.485 393.476 112.808 335.952 112.808 264.972C112.808 193.99 170.485 136.467 241.657 136.467C260.344 136.467 278.128 140.458 294.185 147.634L294.241 147.657ZM241.657 386.313C174.402 386.313 119.99 332.046 119.99 264.972C119.99 197.898 174.402 143.63 241.657 143.63C308.909 143.63 363.322 197.898 363.322 264.972C363.322 332.046 308.691 386.313 241.657 386.313ZM364.844 162.294C364.844 161.643 364.626 160.558 363.974 159.473C362.45 161.643 360.926 163.814 359.62 166.419C361.144 166.419 362.885 165.985 363.754 165.333C364.191 164.899 364.844 164.248 364.844 162.294ZM315.439 126.478C312.609 126.044 310.867 126.478 310.215 127.129C308.909 128.215 308.691 129.734 308.909 131.253C311.085 129.734 313.261 128.215 315.439 126.478ZM303.248 241.528C333.937 244.13 357.224 250.639 357.224 263.88C357.224 327.48 305.424 379.143 241.654 379.143C178.472 379.143 126.827 328.43 125.878 265.639C125.869 265.415 125.864 265.193 125.864 264.97C125.864 264.861 125.865 264.752 125.867 264.643C125.865 264.389 125.865 264.135 125.865 263.88H125.895C126.724 249.363 144.485 249.863 176.141 253.465C190.724 255.202 210.748 256.287 232.948 249.776C258.63 241.961 281.048 239.789 303.248 241.528ZM235.454 258.165L235.413 258.178C211.591 265.165 190.228 263.959 175.129 262.163C158.617 260.285 147.335 259.463 140.246 260.615C136.818 261.174 135.589 262.026 135.234 262.367L135.223 262.378C135.114 262.478 134.621 262.928 134.621 264.97C134.621 267.6 136.016 271.543 140.491 276.233C144.895 280.85 151.75 285.583 161.037 289.563C179.564 297.507 206.757 302.015 239.989 297.326C264.652 293.811 279.954 287.83 294.907 281.987C300.563 279.776 306.167 277.585 312.211 275.554L312.237 275.546L312.263 275.537C318.22 273.576 323.933 272.139 328.974 270.926C329.88 270.707 330.759 270.498 331.607 270.296L331.676 270.278L331.696 270.274C335.654 269.33 338.978 268.537 341.872 267.661C345.498 266.561 347.309 265.641 348.139 264.98C348.18 264.946 348.217 264.915 348.248 264.889C348.194 264.054 347.263 260.978 338.357 257.378C329.937 253.976 317.504 251.526 302.535 250.254C281.474 248.611 260.161 250.648 235.496 258.152L235.454 258.165ZM279.526 210.272C290.346 210.272 299.113 201.525 299.113 190.735C299.113 179.946 290.346 171.199 279.526 171.199C268.709 171.199 259.937 179.946 259.937 190.735C259.937 201.525 268.709 210.272 279.526 210.272ZM319.791 216.574C319.791 221.13 316.087 224.824 311.52 224.824C306.952 224.824 303.25 221.13 303.25 216.574C303.25 212.02 306.952 208.326 311.52 208.326C316.087 208.326 319.791 212.02 319.791 216.574Z"
fill="white"
/>
</g>
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_207_398" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="500" height="500">
<path d="M250 500C388.071 500 500 388.071 500 250C500 111.929 388.071 0 250 0C111.929 0 0 111.929 0 250C0 388.071 111.929 500 250 500Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_207_398)">
<path d="M500 0H0V500H500V0Z" fill="#E50571"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M355.922 116.063C373.552 128.87 383.346 140.375 386.393 151.879C388.352 158.391 386.611 166.206 382.259 172.067C377.904 177.927 370.941 181.183 362.887 181.183C359.841 181.183 356.57 180.717 353.526 179.849C354.448 177.382 355.467 174.831 356.572 172.28C357.443 172.497 358.533 172.497 359.62 172.497C362.23 172.497 365.278 172.062 367.674 170.109C369.413 168.807 371.374 166.419 371.157 162.077C371.157 157.953 367.891 152.743 361.144 146.448C356.357 141.89 349.826 136.897 343.298 132.339C324.578 119.966 311.52 116.493 305.643 122.354C301.726 126.261 302.161 131.037 303.467 134.51C300.741 136.294 298.183 137.878 295.841 139.276C290.198 130.82 289.907 120.621 294.763 111.505C297.809 105.644 302.598 101.737 308.039 100.651C310.215 100.217 312.391 100 314.787 100C326.757 100 341.774 105.861 355.922 116.063ZM294.241 147.657C301.204 143.967 311.302 137.548 322.837 128.432C327.191 130.168 332.85 133.207 339.815 137.766C348.085 143.192 355.267 149.27 359.839 154.046C352.233 164.014 347.23 177.66 344.183 187.208C360.687 208.796 370.502 235.743 370.502 264.972C370.502 335.952 312.826 393.476 241.657 393.476C170.485 393.476 112.808 335.952 112.808 264.972C112.808 193.99 170.485 136.467 241.657 136.467C260.344 136.467 278.128 140.458 294.185 147.634L294.241 147.657ZM241.657 386.313C174.402 386.313 119.99 332.046 119.99 264.972C119.99 197.898 174.402 143.63 241.657 143.63C308.909 143.63 363.322 197.898 363.322 264.972C363.322 332.046 308.691 386.313 241.657 386.313ZM364.844 162.294C364.844 161.643 364.626 160.558 363.974 159.473C362.45 161.643 360.926 163.814 359.62 166.419C361.144 166.419 362.885 165.985 363.754 165.333C364.191 164.899 364.844 164.248 364.844 162.294ZM315.439 126.478C312.609 126.044 310.867 126.478 310.215 127.129C308.909 128.215 308.691 129.734 308.909 131.253C311.085 129.734 313.261 128.215 315.439 126.478ZM303.248 241.528C333.937 244.13 357.224 250.639 357.224 263.88C357.224 327.48 305.424 379.143 241.654 379.143C178.472 379.143 126.827 328.43 125.878 265.639C125.869 265.415 125.864 265.193 125.864 264.97C125.864 264.861 125.865 264.752 125.867 264.643C125.865 264.389 125.865 264.135 125.865 263.88H125.895C126.724 249.363 144.485 249.863 176.141 253.465C190.724 255.202 210.748 256.287 232.948 249.776C258.63 241.961 281.048 239.789 303.248 241.528ZM235.454 258.165L235.413 258.178C211.591 265.165 190.228 263.959 175.129 262.163C158.617 260.285 147.335 259.463 140.246 260.615C136.818 261.174 135.589 262.026 135.234 262.367L135.223 262.378C135.114 262.478 134.621 262.928 134.621 264.97C134.621 267.6 136.016 271.543 140.491 276.233C144.895 280.85 151.75 285.583 161.037 289.563C179.564 297.507 206.757 302.015 239.989 297.326C264.652 293.811 279.954 287.83 294.907 281.987C300.563 279.776 306.167 277.585 312.211 275.554L312.237 275.546L312.263 275.537C318.22 273.576 323.933 272.139 328.974 270.926C329.88 270.707 330.759 270.498 331.607 270.296L331.676 270.278L331.696 270.274C335.654 269.33 338.978 268.537 341.872 267.661C345.498 266.561 347.309 265.641 348.139 264.98C348.18 264.946 348.217 264.915 348.248 264.889C348.194 264.054 347.263 260.978 338.357 257.378C329.937 253.976 317.504 251.526 302.535 250.254C281.474 248.611 260.161 250.648 235.496 258.152L235.454 258.165ZM279.526 210.272C290.346 210.272 299.113 201.525 299.113 190.735C299.113 179.946 290.346 171.199 279.526 171.199C268.709 171.199 259.937 179.946 259.937 190.735C259.937 201.525 268.709 210.272 279.526 210.272ZM319.791 216.574C319.791 221.13 316.087 224.824 311.52 224.824C306.952 224.824 303.25 221.13 303.25 216.574C303.25 212.02 306.952 208.326 311.52 208.326C316.087 208.326 319.791 212.02 319.791 216.574Z" fill="white"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,4 +1,13 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 120 120"
style="enable-background: new 0 0 120 120"
xml:space="preserve"
>
<path fill="#2775CA" d="M60,120c33.2,0,60-26.8,60-60S93.2,0,60,0S0,26.8,0,60S26.8,120,60,120z" />
<path
fill="#FFFFFF"

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +1,4 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 339.43 295.27">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 339.43 295.27">
<path
d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z"
fill="#50af95"

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -40,16 +40,6 @@
cursor: pointer;
}
.layout__area--center {
background: var(--tv-background) !important;
}
.chart-widget.chart-widget--themed-dark.chart-widget__top--themed-dark.chart-widget__bottom--themed-dark
> table
canvas {
background: transparent !important;
}
/* Floating menu */
.floating-toolbar-react-widgets__button:hover,
[class^='button-']:hover:before {

View File

@ -1,30 +1,23 @@
import { cacheFn, positionsCache } from 'api/cache'
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import getPrices from 'api/prices/getPrices'
import getDepositedVaults from 'api/vaults/getDepositedVaults'
import { BNCoin } from 'types/classes/BNCoin'
import { Positions } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { resolvePerpsPositions } from 'utils/resolvers'
export default async function getAccount(
chainConfig: ChainConfig,
accountId?: string,
): Promise<Account> {
export default async function getAccount(accountId?: string): Promise<Account> {
if (!accountId) return new Promise((_, reject) => reject('No account ID found'))
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
const creditManagerQueryClient = await getCreditManagerQueryClient()
const accountPosition: Positions = await cacheFn(
() => creditManagerQueryClient.positions({ accountId: accountId }),
positionsCache,
`${chainConfig.id}/account/${accountId}`,
`account/${accountId}`,
)
const prices = await getPrices(chainConfig)
const accountKind = await creditManagerQueryClient.accountKind({ accountId: accountId })
const depositedVaults = await getDepositedVaults(accountId, chainConfig, accountPosition)
const depositedVaults = await getDepositedVaults(accountId, accountPosition)
if (accountPosition) {
return {
@ -33,7 +26,6 @@ export default async function getAccount(
lends: accountPosition.lends.map((lend) => new BNCoin(lend)),
deposits: accountPosition.deposits.map((deposit) => new BNCoin(deposit)),
vaults: depositedVaults,
perps: resolvePerpsPositions(accountPosition.perps, prices),
kind: accountKind,
}
}

View File

@ -61,4 +61,3 @@ export const allParamsCache: Cache<AssetParamsBaseForAddr[]> = new Map()
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()

View File

@ -1,149 +1,163 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { ENV } from 'constants/env'
import { ICNSQueryClient } from 'types/classes/ICNSClient.client'
import { MarsAccountNftQueryClient } from 'types/generated/mars-account-nft/MarsAccountNft.client'
import { MarsCreditManagerQueryClient } from 'types/generated/mars-credit-manager/MarsCreditManager.client'
import { MarsIncentivesQueryClient } from 'types/generated/mars-incentives/MarsIncentives.client'
import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client'
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'
let _cosmWasmClient: Map<string, CosmWasmClient> = new Map()
let _creditManagerQueryClient: Map<string, MarsCreditManagerQueryClient> = new Map()
let _oracleQueryClient: Map<string, MarsOracleOsmosisQueryClient> = new Map()
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 _cosmWasmClient: CosmWasmClient
let _accountNftQueryClient: MarsAccountNftQueryClient
let _creditManagerQueryClient: MarsCreditManagerQueryClient
let _oracleQueryClient: MarsOracleOsmosisQueryClient
let _redBankQueryClient: MarsRedBankQueryClient
let _paramsQueryClient: MarsParamsQueryClient
let _incentivesQueryClient: MarsIncentivesQueryClient
let _swapperOsmosisClient: MarsSwapperOsmosisQueryClient
let _ICNSQueryClient: ICNSQueryClient
const getClient = async (rpc: string) => {
const getClient = async () => {
try {
if (!_cosmWasmClient.get(rpc)) {
const client = await CosmWasmClient.connect(rpc)
_cosmWasmClient.set(rpc, client)
if (!_cosmWasmClient) {
_cosmWasmClient = await CosmWasmClient.connect(ENV.URL_RPC)
}
return _cosmWasmClient.get(rpc)!
return _cosmWasmClient
} catch (error) {
throw error
}
}
const getCreditManagerQueryClient = async (chainConfig: ChainConfig) => {
const getAccountNftQueryClient = async () => {
try {
const contract = chainConfig.contracts.creditManager
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_creditManagerQueryClient.get(key)) {
const client = await getClient(rpc)
_creditManagerQueryClient.set(key, new MarsCreditManagerQueryClient(client, contract))
if (!_accountNftQueryClient) {
const client = await getClient()
_accountNftQueryClient = new MarsAccountNftQueryClient(client, ENV.ADDRESS_ACCOUNT_NFT)
}
return _creditManagerQueryClient.get(key)!
return _accountNftQueryClient
} catch (error) {
throw error
}
}
const getParamsQueryClient = async (chainConfig: ChainConfig) => {
const getCreditManagerQueryClient = async () => {
try {
const contract = chainConfig.contracts.params
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_paramsQueryClient.get(key)) {
const client = await getClient(rpc)
_paramsQueryClient.set(key, new MarsParamsQueryClient(client, contract))
if (!_creditManagerQueryClient) {
const client = await getClient()
_creditManagerQueryClient = new MarsCreditManagerQueryClient(
client,
ENV.ADDRESS_CREDIT_MANAGER,
)
}
return _paramsQueryClient.get(key)!
return _creditManagerQueryClient
} catch (error) {
throw error
}
}
const getOracleQueryClient = async (chainConfig: ChainConfig) => {
const getParamsQueryClient = async () => {
try {
const contract = chainConfig.contracts.oracle
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_oracleQueryClient.get(key)) {
const client = await getClient(rpc)
_oracleQueryClient.set(key, new MarsOracleOsmosisQueryClient(client, contract))
if (!_paramsQueryClient) {
const client = await getClient()
_paramsQueryClient = new MarsParamsQueryClient(client, ENV.ADDRESS_PARAMS)
}
return _oracleQueryClient.get(key)!
return _paramsQueryClient
} catch (error) {
throw error
}
}
const getVaultQueryClient = async (chainConfig: ChainConfig, address: string) => {
const getOracleQueryClient = async () => {
try {
const client = await getClient(chainConfig.endpoints.rpc)
if (!_oracleQueryClient) {
const client = await getClient()
_oracleQueryClient = new MarsOracleOsmosisQueryClient(client, ENV.ADDRESS_ORACLE)
}
return _oracleQueryClient
} catch (error) {
throw error
}
}
const getRedBankQueryClient = async () => {
try {
if (!_redBankQueryClient) {
const client = await getClient()
_redBankQueryClient = new MarsRedBankQueryClient(client, ENV.ADDRESS_RED_BANK)
}
return _redBankQueryClient
} catch (error) {
throw error
}
}
const getVaultQueryClient = async (address: string) => {
try {
const client = await getClient()
return new MarsMockVaultQueryClient(client, address)
} catch (error) {
throw error
}
}
const getIncentivesQueryClient = async (chainConfig: ChainConfig) => {
const getIncentivesQueryClient = async () => {
try {
const contract = chainConfig.contracts.incentives
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_incentivesQueryClient.get(key)) {
const client = await getClient(rpc)
_incentivesQueryClient.set(key, new MarsIncentivesQueryClient(client, contract))
if (!_incentivesQueryClient) {
const client = await getClient()
_incentivesQueryClient = new MarsIncentivesQueryClient(client, ENV.ADDRESS_INCENTIVES)
}
return _incentivesQueryClient.get(key)!
return _incentivesQueryClient
} catch (error) {
throw error
}
}
const getSwapperQueryClient = async (chainConfig: ChainConfig) => {
const getSwapperQueryClient = async () => {
try {
const contract = chainConfig.contracts.swapper
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_swapperOsmosisClient.get(key)) {
const client = await getClient(rpc)
_swapperOsmosisClient.set(key, new MarsSwapperOsmosisQueryClient(client, contract))
if (!_swapperOsmosisClient) {
const client = await getClient()
_swapperOsmosisClient = new MarsSwapperOsmosisQueryClient(client, ENV.ADDRESS_SWAPPER)
}
return _swapperOsmosisClient.get(key)!
return _swapperOsmosisClient
} catch (error) {
throw error
}
}
const getPerpsQueryClient = async (chainConfig: ChainConfig) => {
const getICNSQueryClient = async () => {
try {
const contract = chainConfig.contracts.perps
const rpc = chainConfig.endpoints.rpc
const key = rpc + contract
if (!_perpsClient.get(key)) {
const client = await getClient(rpc)
_perpsClient.set(key, new MarsPerpsQueryClient(client, contract))
if (!_ICNSQueryClient) {
const client = await getClient()
_ICNSQueryClient = new ICNSQueryClient(client)
}
return _perpsClient.get(key)!
return _ICNSQueryClient
} catch (error) {
throw error
}
}
export {
getAccountNftQueryClient,
getClient,
getCreditManagerQueryClient,
getICNSQueryClient,
getIncentivesQueryClient,
getOracleQueryClient,
getParamsQueryClient,
getRedBankQueryClient,
getSwapperQueryClient,
getVaultQueryClient,
getPerpsQueryClient,
}

View File

@ -1,8 +1,9 @@
import { cacheFn, stakingAprCache } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getStakingAprs(url: string) {
export default async function getStakingAprs() {
try {
return cacheFn(() => fetchAprs(url), stakingAprCache, `stakingAprs`)
return cacheFn(() => fetchAprs(), stakingAprCache, `stakingAprs`)
} catch (error) {
throw error
}
@ -12,7 +13,9 @@ interface StakingAprResponse {
stats: StakingApr[]
}
async function fetchAprs(url: string): Promise<StakingApr[]> {
async function fetchAprs(): Promise<StakingApr[]> {
const url = ENV.STRIDE_APRS
const response = await fetch(url)
const body = (await response.json()) as StakingAprResponse
return body.stats

View File

@ -4,29 +4,24 @@ import getAccounts from 'api/wallets/getAccounts'
import { calculateAccountLeverage, getAccountPositionValues, isAccountEmpty } from 'utils/accounts'
export default async function getHLSStakingAccounts(
chainConfig: ChainConfig,
address?: string,
): Promise<HLSAccountWithStrategy[]> {
const accounts = await getAccounts('high_levered_strategy', chainConfig, address)
const accounts = await getAccounts('high_levered_strategy', address)
const activeAccounts = accounts.filter((account) => !isAccountEmpty(account))
const hlsStrategies = await getHLSStakingAssets(chainConfig)
const prices = await getPrices(chainConfig)
const hlsStrategies = await getHLSStakingAssets()
const prices = await getPrices()
const hlsAccountsWithStrategy: HLSAccountWithStrategy[] = []
activeAccounts.forEach((account) => {
if (account.deposits.length === 0) return
const strategy = hlsStrategies.find(
(strategy) => strategy.denoms.deposit === account.deposits[0].denom,
(strategy) => strategy.denoms.deposit === account.deposits.at(0).denom,
)
if (!strategy) return
const [deposits, lends, debts, vaults] = getAccountPositionValues(
account,
prices,
chainConfig.assets,
)
const [deposits, lends, debts, vaults] = getAccountPositionValues(account, prices)
hlsAccountsWithStrategy.push({
...account,
@ -36,7 +31,7 @@ export default async function getHLSStakingAccounts(
debt: debts,
total: deposits,
},
leverage: calculateAccountLeverage(account, prices, chainConfig.assets).toNumber(),
leverage: calculateAccountLeverage(account, prices).toNumber(),
})
})

View File

@ -1,29 +1,31 @@
import { getParamsQueryClient } from 'api/cosmwasm-client'
import getStakingAprs from 'api/hls/getAprs'
import getAssetParams from 'api/params/getAssetParams'
import { byDenom } from 'utils/array'
import { getAssetByDenom, getStakingAssets } from 'utils/assets'
import { BN } from 'utils/helpers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getHLSStakingAssets(chainConfig: ChainConfig) {
const stakingAssetDenoms = chainConfig.assets
.filter((asset) => asset.isStaking)
.map((asset) => asset.denom)
const assetParams = await getAssetParams(chainConfig)
export default async function getHLSStakingAssets() {
const stakingAssetDenoms = getStakingAssets().map((asset) => asset.denom)
console.log('stakingAssetDenoms', stakingAssetDenoms)
const assetParams = await getAssetParams()
console.log('assetParams', assetParams)
const HLSAssets = assetParams
.filter((asset) => stakingAssetDenoms.includes(asset.denom))
.filter((asset) => asset.credit_manager.hls)
console.log('HLSAssets', HLSAssets)
const strategies = resolveHLSStrategies('coin', HLSAssets)
const client = await getParamsQueryClient(chainConfig)
console.log('strategies', strategies)
const client = await getParamsQueryClient()
const depositCaps$ = strategies.map((strategy) =>
client.totalDeposit({ denom: strategy.denoms.deposit }),
)
const aprs = await getStakingAprs(chainConfig.endpoints.aprs.stride)
const aprs = await getStakingAprs()
return Promise.all(depositCaps$).then((depositCaps) => {
return depositCaps.map((depositCap, index) => {
const borrowSymbol = chainConfig.assets.find(byDenom(strategies[index].denoms.borrow))?.symbol
const borrowSymbol = getAssetByDenom(strategies[index].denoms.borrow)?.symbol
return {
...strategies[index],
depositCap: {

View File

@ -4,10 +4,10 @@ import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { BN } from 'utils/helpers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getHLSVaults(chainConfig: ChainConfig) {
const assetParams = await getAssetParams(chainConfig)
const client = await getCreditManagerQueryClient(chainConfig)
const vaultConfigs = await getVaultConfigs(chainConfig)
export default async function getHLSVaults() {
const assetParams = await getAssetParams()
const client = await getCreditManagerQueryClient()
const vaultConfigs = await getVaultConfigs()
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const strategies = resolveHLSStrategies('vault', HLSAssets)

View File

@ -0,0 +1,41 @@
import getMarket from 'api/markets/getMarket'
import getTotalActiveEmissionValue from 'api/incentives/getTotalActiveEmissionValue'
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
import { BN } from 'utils/helpers'
import { SECONDS_IN_A_YEAR } from 'utils/constants'
import getPrice from 'api/prices/getPrice'
import { ASSETS } from 'constants/assets'
import { byDenom } from 'utils/array'
export default async function calculateAssetIncentivesApy(
denom: string,
): Promise<BigNumber | null> {
try {
const [totalActiveEmissionValue, market] = await Promise.all([
getTotalActiveEmissionValue(denom),
getMarket(denom),
])
if (!totalActiveEmissionValue) return null
const [marketLiquidityAmount, assetPrice] = await Promise.all([
getUnderlyingLiquidityAmount(market),
getPrice(denom),
])
const assetDecimals = (ASSETS.find(byDenom(denom)) as Asset).decimals
const marketLiquidityValue = BN(marketLiquidityAmount)
.shiftedBy(-assetDecimals)
.multipliedBy(assetPrice)
const marketReturns = BN(market.liquidityRate).multipliedBy(marketLiquidityValue)
const annualEmission = totalActiveEmissionValue.multipliedBy(SECONDS_IN_A_YEAR)
const totalAnnualReturnsValue = annualEmission.plus(marketReturns)
return totalAnnualReturnsValue.dividedBy(marketLiquidityValue).multipliedBy(100)
} catch (ex) {
console.error(ex)
return null
}
}

View File

@ -0,0 +1,42 @@
import { cacheFn, emissionsCache } from 'api/cache'
import { getIncentivesQueryClient } from 'api/cosmwasm-client'
import getPrice from 'api/prices/getPrice'
import { ASSETS } from 'constants/assets'
import { BN_ZERO } from 'constants/math'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
export default async function getTotalActiveEmissionValue(
denom: string,
): Promise<BigNumber | null> {
try {
const client = await getIncentivesQueryClient()
const activeEmissions = await cacheFn(
() =>
client.activeEmissions({
collateralDenom: denom,
}),
emissionsCache,
`emission/${denom}`,
60,
)
if (activeEmissions.length === 0) {
throw 'Asset has no active incentive emission.'
}
const prices = await Promise.all(
activeEmissions.map((activeEmission) => getPrice(activeEmission.denom)),
)
return activeEmissions.reduce((accumulation, current, index) => {
const price = prices[index]
const decimals = ASSETS.find(byDenom(current.denom))?.decimals as number
const emissionValue = BN(current.emission_rate).shiftedBy(-decimals).multipliedBy(price)
return accumulation.plus(emissionValue)
}, BN_ZERO)
} catch (ex) {
return null
}
}

View File

@ -0,0 +1,31 @@
import { cacheFn, unclaimedRewardsCache } from 'api/cache'
import { getIncentivesQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { BNCoin } from 'types/classes/BNCoin'
import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getUnclaimedRewards(accountId: string): Promise<BNCoin[]> {
try {
const client = await getIncentivesQueryClient()
const unclaimedRewards = await cacheFn(
() =>
iterateContractQuery(() =>
client.userUnclaimedRewards({
user: ENV.ADDRESS_CREDIT_MANAGER,
accountId,
}),
),
unclaimedRewardsCache,
`incentives/${accountId}`,
60,
)
if (unclaimedRewards.length === 0) return []
return await Promise.all(
unclaimedRewards.map((reward) => new BNCoin({ denom: reward.denom, amount: reward.amount })),
)
} catch (ex) {
return []
}
}

View File

@ -0,0 +1,24 @@
import { cacheFn, marketCache } from 'api/cache'
import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client'
import { resolveMarketResponse } from 'utils/resolvers'
export default async function getMarket(denom: string): Promise<Market> {
return cacheFn(() => fetchMarket(denom), marketCache, denom, 60)
}
async function fetchMarket(denom: string) {
try {
const redBankClient = await getRedBankQueryClient()
const paramsClient = await getParamsQueryClient()
const [market, assetParams, assetCap] = await Promise.all([
redBankClient.market({ denom }),
paramsClient.assetParams({ denom }),
paramsClient.totalDeposit({ denom }),
])
return resolveMarketResponse(market, assetParams, assetCap)
} catch (ex) {
throw ex
}
}

View File

@ -0,0 +1,33 @@
import getMarketLiquidities from 'api/markets/getMarketLiquidities'
import getMarkets from 'api/markets/getMarkets'
import getPrices from 'api/prices/getPrices'
import { getEnabledMarketAssets } from 'utils/assets'
import { BN } from 'utils/helpers'
export default async function getMarketBorrowings(): Promise<BorrowAsset[]> {
const liquidities = await getMarketLiquidities()
const enabledAssets = getEnabledMarketAssets()
const borrowEnabledMarkets = (await getMarkets()).filter((market: Market) => market.borrowEnabled)
const prices = await getPrices()
const borrow: BorrowAsset[] = borrowEnabledMarkets.map((market) => {
const price = prices.find((coin) => coin.denom === market.denom)?.amount ?? '1'
const amount = liquidities.find((coin) => coin.denom === market.denom)?.amount ?? '0'
const asset = enabledAssets.find((asset) => asset.denom === market.denom)!
return {
...asset,
borrowRate: market.borrowRate ?? 0,
liquidity: {
amount: BN(amount),
value: BN(amount).multipliedBy(price),
},
}
})
if (borrow) {
return borrow
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -0,0 +1,31 @@
import { cacheFn, underlyingDebtCache } from 'api/cache'
import { getRedBankQueryClient } from 'api/cosmwasm-client'
import getMarkets from 'api/markets/getMarkets'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketDebts(): Promise<BNCoin[]> {
try {
const markets: Market[] = await getMarkets()
const redBankQueryClient = await getRedBankQueryClient()
const debtQueries = markets.map((asset) =>
cacheFn(
() =>
redBankQueryClient.underlyingDebtAmount({
denom: asset.denom,
amountScaled: asset.debtTotalScaled,
}),
underlyingDebtCache,
`marketDebts/${asset.denom}/amount/${asset.debtTotalScaled}`,
60,
),
)
const debtsResults = await Promise.all(debtQueries)
return debtsResults.map<BNCoin>(
(debt, index) => new BNCoin({ denom: markets[index].denom, amount: debt }),
)
} catch (ex) {
throw ex
}
}

View File

@ -0,0 +1,21 @@
import getMarkets from 'api/markets/getMarkets'
import getUnderlyingLiquidityAmount from 'api/markets/getMarketUnderlyingLiquidityAmount'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketDeposits(): Promise<BNCoin[]> {
try {
const markets: Market[] = await getMarkets()
const depositQueries = markets.map(getUnderlyingLiquidityAmount)
const depositsResults = await Promise.all(depositQueries)
return depositsResults.map<BNCoin>(
(deposit, index) =>
new BNCoin({
denom: markets[index].denom,
amount: deposit,
}),
)
} catch (ex) {
throw ex
}
}

View File

@ -0,0 +1,31 @@
import { BN } from 'utils/helpers'
import getMarketDeposits from 'api/markets/getMarketDeposits'
import getMarketDebts from 'api/markets/getMarketDebts'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getMarketLiquidities(): Promise<BNCoin[]> {
const deposits = await getMarketDeposits()
const debts = await getMarketDebts()
const liquidity: BNCoin[] = deposits.map((deposit) => {
const debt = debts.find((debt) => debt.denom === deposit.denom)
if (debt) {
return new BNCoin({
denom: deposit.denom,
amount: deposit.amount.minus(debt.amount).toString(),
})
}
return new BNCoin({
denom: deposit.denom,
amount: '0',
})
})
if (liquidity) {
return liquidity
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -0,0 +1,23 @@
import { cacheFn, underlyingLiquidityAmountCache } from 'api/cache'
import { getRedBankQueryClient } from 'api/cosmwasm-client'
export default async function getUnderlyingLiquidityAmount(market: Market): Promise<string> {
return cacheFn(
() => fetchUnderlyingLiquidityAmount(market),
underlyingLiquidityAmountCache,
`underlyingLiquidity/${market.denom}/amount/${market.collateralTotalScaled}`,
60,
)
}
async function fetchUnderlyingLiquidityAmount(market: Market) {
try {
const client = await getRedBankQueryClient()
return await client.underlyingLiquidityAmount({
denom: market.denom,
amountScaled: market.collateralTotalScaled,
})
} catch (ex) {
throw ex
}
}

View File

@ -0,0 +1,48 @@
import { allParamsCache, cacheFn, marketsCache, totalDepositCache } from 'api/cache'
import { getParamsQueryClient, getRedBankQueryClient } from 'api/cosmwasm-client'
import {
AssetParamsBaseForAddr as AssetParams,
TotalDepositResponse,
} from 'types/generated/mars-params/MarsParams.types'
import { Market as RedBankMarket } from 'types/generated/mars-red-bank/MarsRedBank.types'
import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
import iterateContractQuery from 'utils/iterateContractQuery'
import { resolveMarketResponse } from 'utils/resolvers'
export default async function getMarkets(): Promise<Market[]> {
try {
const redBankClient = await getRedBankQueryClient()
const paramsClient = await getParamsQueryClient()
const enabledAssets = getEnabledMarketAssets()
const capQueries = enabledAssets.map((asset) =>
cacheFn(
() => paramsClient.totalDeposit({ denom: asset.denom }),
totalDepositCache,
`enabledMarkets/${asset.denom}`,
60,
),
)
const [markets, assetParams, assetCaps] = await Promise.all([
cacheFn(() => iterateContractQuery(redBankClient.markets), marketsCache, 'markets', 60),
cacheFn(
async () => await iterateContractQuery(paramsClient.allAssetParams),
allParamsCache,
'params',
60,
),
Promise.all(capQueries),
])
return enabledAssets.map((asset) =>
resolveMarketResponse(
markets.find(byDenom(asset.denom)) as RedBankMarket,
assetParams.find(byDenom(asset.denom)) as AssetParams,
assetCaps.find(byDenom(asset.denom)) as TotalDepositResponse,
),
)
} catch (ex) {
throw ex
}
}

View File

@ -1,21 +1,11 @@
import { assetParamsCache, cacheFn } from 'api/cache'
import { getParamsQueryClient } from 'api/cosmwasm-client'
import { AssetParamsBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getAssetParams(
chainConfig: ChainConfig,
): Promise<AssetParamsBaseForAddr[]> {
export default async function getAssetParams(): Promise<AssetParamsBaseForAddr[]> {
try {
return await cacheFn(
async () => {
const paramsQueryClient = await getParamsQueryClient(chainConfig)
return iterateContractQuery(paramsQueryClient.allAssetParams)
},
assetParamsCache,
`${chainConfig.id}/assetParams`,
600,
)
const paramsQueryClient = await getParamsQueryClient()
return iterateContractQuery(paramsQueryClient.allAssetParams)
} catch (ex) {
throw ex
}

View File

@ -1,14 +0,0 @@
import { getPerpsQueryClient } from 'api/cosmwasm-client'
import { BNCoin } from 'types/classes/BNCoin'
export default async function getOpeningFee(
chainConfig: ChainConfig,
denom: string,
amount: string,
) {
const perpsClient = await getPerpsQueryClient(chainConfig)
return perpsClient
.openingFee({ denom, size: amount as any })
.then((resp) => BNCoin.fromCoin(resp.fee))
}

View File

@ -1,10 +1,11 @@
import getPoolPrice from 'api/prices/getPoolPrice'
import { ASSETS } from 'constants/assets'
import { bySymbol } from 'utils/array'
async function getMarsPrice(chainConfig: ChainConfig) {
const marsAsset = chainConfig.assets.find(bySymbol('MARS'))
async function getMarsPrice() {
const marsAsset = ASSETS.find(bySymbol('MARS'))
if (!marsAsset) return 0
return await getPoolPrice(chainConfig, marsAsset)
return await getPoolPrice(marsAsset)
}
export default getMarsPrice

View File

@ -1,6 +1,5 @@
import { cacheFn, oraclePriceCache } from 'api/cache'
import { getOracleQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { BNCoin } from 'types/classes/BNCoin'
import { PriceResponse } from 'types/generated/mars-oracle-osmosis/MarsOracleOsmosis.types'
@ -8,18 +7,15 @@ import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
import iterateContractQuery from 'utils/iterateContractQuery'
export default async function getOraclePrices(
chainConfig: ChainConfig,
assets: Asset[],
): Promise<BNCoin[]> {
export default async function getOraclePrices(...assets: Asset[]): Promise<BNCoin[]> {
try {
if (!assets.length) return []
const oracleQueryClient = await getOracleQueryClient(chainConfig)
const oracleQueryClient = await getOracleQueryClient()
const priceResults = await cacheFn(
() => iterateContractQuery(oracleQueryClient.prices),
oraclePriceCache,
`${chainConfig.id}/oraclePrices`,
'oraclePrices',
60,
)
@ -28,7 +24,7 @@ export default async function getOraclePrices(
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BNCoin.fromDenomAndBigNumber(
asset.denom,
BN(priceResponse?.price ?? BN_ZERO).shiftedBy(decimalDiff),
BN(priceResponse.price).shiftedBy(decimalDiff),
)
})
} catch (ex) {

View File

@ -1,5 +1,6 @@
import { cacheFn, poolPriceCache } from 'api/cache'
import getPrice from 'api/prices/getPrice'
import { ENV } from 'constants/env'
import { BN_ONE } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { byDenom, byTokenDenom, partition } from 'utils/array'
@ -16,29 +17,28 @@ interface PoolAsset {
}
export default async function getPoolPrice(
chainConfig: ChainConfig,
asset: Asset,
lookupPricesForBaseAsset?: BNCoin[],
): Promise<BigNumber> {
if (!asset.poolId) throw 'given asset should have a poolId to fetch the price'
const [assetRate, baseAsset] = await getAssetRate(chainConfig, asset)
const [assetRate, baseAsset] = await getAssetRate(asset)
const baseAssetPrice =
(lookupPricesForBaseAsset &&
lookupPricesForBaseAsset.find(byDenom(baseAsset.token.denom))?.amount) ||
(await getPrice(chainConfig, baseAsset.token.denom))
(await getPrice(baseAsset.token.denom))
if (!baseAssetPrice) throw 'base asset price must be available on Pyth or in Oracle contract'
return assetRate.multipliedBy(baseAssetPrice)
}
const getAssetRate = async (chainConfig: ChainConfig, asset: Asset) => {
const url = chainConfig.endpoints.pools.replace('POOL_ID', asset.poolId!.toString())
const getAssetRate = async (asset: Asset) => {
const url = `${ENV.URL_REST}osmosis/gamm/v1beta1/pools/${asset.poolId}`
const response = await cacheFn(
() => fetch(url).then((res) => res.json()),
poolPriceCache,
`${chainConfig.id}/poolPrices/${(asset.poolId || 0).toString()}`,
`poolPrices/${(asset.poolId || 0).toString()}`,
60,
)
const pool = response.pool

View File

@ -1,20 +1,37 @@
import { cacheFn, priceCache } from 'api/cache'
import getPrices from 'api/prices/getPrices'
import { BN_ZERO } from 'constants/math'
import { getOracleQueryClient } from 'api/cosmwasm-client'
import getPoolPrice from 'api/prices/getPoolPrice'
import getPythPrice from 'api/prices/getPythPrices'
import { ASSETS } from 'constants/assets'
import { PRICE_ORACLE_DECIMALS } from 'constants/query'
import { byDenom } from 'utils/array'
import { BN } from 'utils/helpers'
export default async function getPrice(
chainConfig: ChainConfig,
denom: string,
): Promise<BigNumber> {
return cacheFn(() => fetchPrice(chainConfig, denom), priceCache, `price/${denom}`, 60)
export default async function getPrice(denom: string): Promise<BigNumber> {
return cacheFn(() => fetchPrice(denom), priceCache, `price/${denom}`, 60)
}
async function fetchPrice(chainConfig: ChainConfig, denom: string) {
async function fetchPrice(denom: string) {
try {
const prices = await getPrices(chainConfig)
const asset = ASSETS.find(byDenom(denom)) as Asset
return prices.find(byDenom(denom))?.amount ?? BN_ZERO
if (asset.pythPriceFeedId) {
return (await getPythPrice(asset.pythPriceFeedId))[0]
}
if (asset.hasOraclePrice) {
const oracleQueryClient = await getOracleQueryClient()
const priceResponse = await oracleQueryClient.price({ denom: asset.denom })
const decimalDiff = asset.decimals - PRICE_ORACLE_DECIMALS
return BN(priceResponse.price).shiftedBy(decimalDiff)
}
if (asset.poolId) {
return await getPoolPrice(asset)
}
throw `could not fetch the price info for the given denom: ${denom}`
} catch (ex) {
throw ex
}

View File

@ -0,0 +1,20 @@
import fetchPythPriceData from 'api/prices/getPythPriceData'
import { getPythAssets } from 'utils/assets'
export default async function getPricesData(): Promise<string[]> {
try {
const assetsWithPythPriceFeedId = getPythAssets()
const pythAndOraclePriceData = await requestPythPriceData(assetsWithPythPriceFeedId)
return pythAndOraclePriceData
} catch (ex) {
console.error(ex)
throw ex
}
}
async function requestPythPriceData(assets: Asset[]): Promise<string[]> {
if (!assets.length) return []
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPriceData(...priceFeedIds)
}

View File

@ -1,35 +1,29 @@
import getOraclePrices from 'api/prices/getOraclePrices'
import getPoolPrice from 'api/prices/getPoolPrice'
import fetchPythPrices from 'api/prices/getPythPrices'
import chains from 'configs/chains'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { partition } from 'utils/array'
import { getAllAssetsWithPythId } from 'utils/assets'
import { getAssetsMustHavePriceInfo } from 'utils/assets'
export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoin[]> {
export default async function getPrices(): Promise<BNCoin[]> {
const usdPrice = new BNCoin({ denom: 'usd', amount: '1' })
const pythAndOraclePrices = []
const assetsToFetchPrices = useStore
.getState()
.chainConfig.assets.filter(
(asset) => (asset.isEnabled && asset.isMarket) || asset.forceFetchPrice,
)
const assetsWithPythPriceFeedId = getAllAssetsWithPythId(chains)
const pythPrices = await requestPythPrices(assetsWithPythPriceFeedId)
pythAndOraclePrices.push(...pythPrices)
try {
const [assetsWithOraclePrices, assetsWithPoolIds] =
const assetsToFetchPrices = getAssetsMustHavePriceInfo()
const [assetsWithPythPriceFeedId, assetsWithOraclePrices, assetsWithPoolIds] =
separateAssetsByPriceSources(assetsToFetchPrices)
const oraclePrices = await getOraclePrices(chainConfig, assetsWithOraclePrices)
const poolPrices = await requestPoolPrices(chainConfig, assetsWithPoolIds, pythAndOraclePrices)
if (oraclePrices) useStore.setState({ isOracleStale: false })
const pythAndOraclePrices = (
await Promise.all([
requestPythPrices(assetsWithPythPriceFeedId),
getOraclePrices(...assetsWithOraclePrices),
])
).flat()
const poolPrices = await requestPoolPrices(assetsWithPoolIds, pythAndOraclePrices)
return [...pythAndOraclePrices, ...oraclePrices, ...poolPrices, usdPrice]
useStore.setState({ isOracleStale: false })
return [...pythAndOraclePrices, ...poolPrices, usdPrice]
} catch (ex) {
console.error(ex)
let message = 'Unknown Error'
@ -37,43 +31,41 @@ export default async function getPrices(chainConfig: ChainConfig): Promise<BNCoi
if (message.includes('price publish time is too old'))
useStore.setState({ isOracleStale: true })
return [...pythAndOraclePrices, usdPrice]
throw ex
}
}
async function requestPythPrices(assets: Asset[]): Promise<BNCoin[]> {
if (!assets.length) return []
const priceFeedIds = assets
.map((a) => a.pythPriceFeedId)
.filter((priceFeedId, index, array) => array.indexOf(priceFeedId) === index) as string[]
return await fetchPythPrices(priceFeedIds, assets)
const priceFeedIds = assets.map((a) => a.pythPriceFeedId) as string[]
return await fetchPythPrices(...priceFeedIds).then(mapResponseToBnCoin(assets))
}
async function requestPoolPrices(
chainConfig: ChainConfig,
assets: Asset[],
lookupPrices: BNCoin[],
): Promise<BNCoin[]> {
const requests = assets.map((asset) => getPoolPrice(chainConfig, asset, lookupPrices))
async function requestPoolPrices(assets: Asset[], lookupPrices: BNCoin[]): Promise<BNCoin[]> {
const requests = assets.map((asset) => getPoolPrice(asset, lookupPrices))
return await Promise.all(requests).then(mapResponseToBnCoin(assets))
}
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) => {
return prices.map((price: BigNumber, index: number) =>
const mapResponseToBnCoin = (assets: Asset[]) => (prices: BigNumber[]) =>
prices.map((price: BigNumber, index: number) =>
BNCoin.fromDenomAndBigNumber(assets[index].denom, price),
)
}
function separateAssetsByPriceSources(assets: Asset[]) {
const assetsWithoutPythPriceFeedId = assets.filter((asset) => !asset.pythPriceFeedId)
// Only fetch Pyth prices for mainnet
const [assetsWithPythPriceFeedId, assetsWithoutPythPriceFeedId] = partition(
assets,
(asset) => !!asset.pythPriceFeedId,
)
// Don't get oracle price if it's not mainnet and there is a poolId
const [assetsWithOraclePrice, assetsWithoutOraclePrice] = partition(
assetsWithoutPythPriceFeedId,
(asset) => asset.hasOraclePrice || !asset.poolId,
)
const assetsWithPoolId = assetsWithoutOraclePrice.filter((asset) => !!asset.poolId)
return [assetsWithOraclePrice, assetsWithPoolId]
return [assetsWithPythPriceFeedId, assetsWithOraclePrice, assetsWithPoolId]
}

View File

@ -1,9 +1,9 @@
import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth'
import { ENV } from 'constants/env'
export default async function getPythPriceData(priceFeedIds: string[]) {
export default async function fetchPythPriceData(...priceFeedIds: string[]) {
try {
const pricesUrl = new URL(`${pythEndpoints.api}/latest_vaas`)
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_vaas`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythDataResponse: string[] = await cacheFn(
@ -14,7 +14,6 @@ export default async function getPythPriceData(priceFeedIds: string[]) {
)
return pythDataResponse
} catch (ex) {
console.log(ex)
return []
throw ex
}
}

View File

@ -1,11 +1,10 @@
import { cacheFn, pythPriceCache } from 'api/cache'
import { pythEndpoints } from 'constants/pyth'
import { BNCoin } from 'types/classes/BNCoin'
import { ENV } from 'constants/env'
import { BN } from 'utils/helpers'
export default async function fetchPythPrices(priceFeedIds: string[], assets: Asset[]) {
export default async function fetchPythPrices(...priceFeedIds: string[]) {
try {
const pricesUrl = new URL(`${pythEndpoints.api}/latest_price_feeds`)
const pricesUrl = new URL(`${ENV.PYTH_ENDPOINT}/latest_price_feeds`)
priceFeedIds.forEach((id) => pricesUrl.searchParams.append('ids[]', id))
const pythResponse: PythPriceData[] = await cacheFn(
@ -15,18 +14,7 @@ export default async function fetchPythPrices(priceFeedIds: string[], assets: As
30,
)
const mappedPriceData = [] as BNCoin[]
assets.forEach((asset) => {
const price = pythResponse.find((pythPrice) => asset.pythPriceFeedId === pythPrice.id)?.price
if (price)
mappedPriceData.push(
BNCoin.fromDenomAndBigNumber(asset.denom, BN(price.price).shiftedBy(price.expo)),
)
return
})
return mappedPriceData
return pythResponse.map(({ price }) => BN(price.price).shiftedBy(price.expo))
} catch (ex) {
throw ex
}

View File

@ -2,13 +2,9 @@ import { getSwapperQueryClient } from 'api/cosmwasm-client'
import { BN_ZERO } from 'constants/math'
import { BN } from 'utils/helpers'
export default async function estimateExactIn(
chainConfig: ChainConfig,
coinIn: Coin,
denomOut: string,
) {
export default async function estimateExactIn(coinIn: Coin, denomOut: string) {
try {
const swapperClient = await getSwapperQueryClient(chainConfig)
const swapperClient = await getSwapperQueryClient()
const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount
return BN(estimatedAmount)

View File

@ -1,53 +0,0 @@
import { BN_ZERO } from 'constants/math'
import { STANDARD_SWAP_FEE } from 'utils/constants'
export default async function getOsmosisSwapFee(
chainConfig: ChainConfig,
poolIds: string[],
): Promise<number> {
const promises = poolIds.map((poolId) =>
fetch(chainConfig.endpoints.pools.replace('POOL_ID', poolId)),
)
const responses = await Promise.all(promises)
const pools = await Promise.all(responses.map(async (pool) => (await pool.json()).pool as Pool))
if (!pools?.length) return STANDARD_SWAP_FEE
return pools
.reduce((acc, pool) => acc.plus(pool?.pool_params?.swap_fee || STANDARD_SWAP_FEE), BN_ZERO)
.toNumber()
}
interface Pool {
'@type': string
address: string
future_pool_governor: string
id: string
pool_assets?: PoolAsset[]
pool_liquidity?: PoolLiquidity[]
pool_params: PoolParams
total_shares: TotalShares
total_weight: string
}
interface PoolAsset {
token: TotalShares
weight: string
}
interface PoolLiquidity {
amount: string
denom: string
}
interface TotalShares {
amount: string
denom: string
}
interface PoolParams {
exit_fee: string
smooth_weight_change_params: null
swap_fee: string
}

View File

@ -1,12 +1,8 @@
import { getSwapperQueryClient } from 'api/cosmwasm-client'
export default async function getSwapRoute(
chainConfig: ChainConfig,
denomIn: string,
denomOut: string,
): Promise<Route[]> {
export default async function getSwapRoute(denomIn: string, denomOut: string): Promise<Route[]> {
try {
const swapperClient = await getSwapperQueryClient(chainConfig)
const swapperClient = await getSwapperQueryClient()
const routes = await swapperClient.route({
denomIn,
denomOut,

View File

@ -21,13 +21,9 @@ import {
import { getCoinValue } from 'utils/formatters'
import { BN } from 'utils/helpers'
async function getUnlocksAtTimestamp(
chainConfig: ChainConfig,
unlockingId: number,
vaultAddress: string,
) {
async function getUnlocksAtTimestamp(unlockingId: number, vaultAddress: string) {
try {
const client = await getClient(chainConfig.endpoints.rpc)
const client = await getClient()
const vaultExtension = (await cacheFn(
() =>
@ -46,7 +42,6 @@ async function getUnlocksAtTimestamp(
}
async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
chainConfig: ChainConfig,
vaultPosition: VaultPosition,
): Promise<[VaultStatus, number | undefined, number | undefined]> {
const amount = vaultPosition.amount
@ -55,11 +50,7 @@ async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
if (amount.locking.unlocking.length) {
const unlockId = amount.locking.unlocking[0].id
const unlocksAtTimestamp = await getUnlocksAtTimestamp(
chainConfig,
unlockId,
vaultPosition.vault.address,
)
const unlocksAtTimestamp = await getUnlocksAtTimestamp(unlockId, vaultPosition.vault.address)
if (moment(unlocksAtTimestamp).isBefore(new Date())) {
return [VaultStatus.UNLOCKED, unlockId, unlocksAtTimestamp]
@ -71,7 +62,7 @@ async function getVaultPositionStatusAndUnlockIdAndUnlockTime(
}
}
export function flatVaultPositionAmount(
function flatVaultPositionAmount(
vaultPositionAmount: VaultPositionAmount,
): VaultPositionFlatAmounts {
const amounts = {
@ -91,14 +82,13 @@ export function flatVaultPositionAmount(
return amounts
}
export async function getLpTokensForVaultPosition(
chainConfig: ChainConfig,
async function getLpTokensForVaultPosition(
vault: Vault,
vaultPosition: VaultPosition,
): Promise<Coin[]> {
try {
const vaultQueryClient = await getVaultQueryClient(chainConfig, vault.address)
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
const vaultQueryClient = await getVaultQueryClient(vault.address)
const creditManagerQueryClient = await getCreditManagerQueryClient()
const amounts = flatVaultPositionAmount(vaultPosition.amount)
const totalAmount = amounts.locked.plus(amounts.unlocked).plus(amounts.unlocking).toString()
@ -143,16 +133,15 @@ export async function getLpTokensForVaultPosition(
async function getVaultValuesAndAmounts(
vault: Vault,
vaultPosition: VaultPosition,
chainConfig: ChainConfig,
): Promise<VaultValuesAndAmounts> {
try {
const pricesQueries = Promise.all([
getPrice(chainConfig, vault.denoms.primary),
getPrice(chainConfig, vault.denoms.secondary),
getPrice(chainConfig, vault.denoms.lp),
getPrice(vault.denoms.primary),
getPrice(vault.denoms.secondary),
getPrice(vault.denoms.lp),
])
const lpTokensQuery = getLpTokensForVaultPosition(chainConfig, vault, vaultPosition)
const lpTokensQuery = getLpTokensForVaultPosition(vault, vaultPosition)
const amounts = flatVaultPositionAmount(vaultPosition.amount)
const [[primaryLpToken, secondaryLpToken], [primaryPrice, secondaryPrice, lpPrice]] =
@ -165,26 +154,18 @@ async function getVaultValuesAndAmounts(
secondary: BN(secondaryLpToken.amount),
},
values: {
primary: getCoinValue(
new BNCoin(primaryLpToken),
[BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice)],
chainConfig.assets,
),
secondary: getCoinValue(
new BNCoin(secondaryLpToken),
[BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice)],
chainConfig.assets,
),
unlocking: getCoinValue(
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking),
[BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice)],
chainConfig.assets,
),
unlocked: getCoinValue(
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked),
[BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice)],
chainConfig.assets,
),
primary: getCoinValue(new BNCoin(primaryLpToken), [
BNCoin.fromDenomAndBigNumber(primaryLpToken.denom, primaryPrice),
]),
secondary: getCoinValue(new BNCoin(secondaryLpToken), [
BNCoin.fromDenomAndBigNumber(secondaryLpToken.denom, secondaryPrice),
]),
unlocking: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocking), [
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
]),
unlocked: getCoinValue(BNCoin.fromDenomAndBigNumber(vault.denoms.lp, amounts.unlocked), [
BNCoin.fromDenomAndBigNumber(vault.denoms.lp, lpPrice),
]),
},
}
} catch (ex) {
@ -194,11 +175,10 @@ async function getVaultValuesAndAmounts(
async function getDepositedVaults(
accountId: string,
chainConfig: ChainConfig,
positions?: Positions,
): Promise<DepositedVault[]> {
try {
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
const creditManagerQueryClient = await getCreditManagerQueryClient()
if (!positions)
positions = await cacheFn(
@ -209,7 +189,7 @@ async function getDepositedVaults(
if (!positions.vaults.length) return []
const [allVaults] = await Promise.all([getVaults(chainConfig)])
const [allVaults] = await Promise.all([getVaults()])
const depositedVaults = positions.vaults.map(async (vaultPosition) => {
const vault = allVaults.find((v) => v.address === vaultPosition.vault.address)
@ -219,8 +199,8 @@ async function getDepositedVaults(
}
const [[status, unlockId, unlocksAt], valuesAndAmounts] = await Promise.all([
getVaultPositionStatusAndUnlockIdAndUnlockTime(chainConfig, vaultPosition),
getVaultValuesAndAmounts(vault, vaultPosition, chainConfig),
getVaultPositionStatusAndUnlockIdAndUnlockTime(vaultPosition),
getVaultValuesAndAmounts(vault, vaultPosition),
])
return {

View File

@ -0,0 +1,26 @@
import { aprsCache, aprsCacheResponse, cacheFn } from 'api/cache'
import { ENV } from 'constants/env'
export default async function getAprs() {
try {
const response = await cacheFn(
() => fetch(ENV.URL_VAULT_APR),
aprsCacheResponse,
'aprsResponse',
60,
)
if (response.ok) {
const data: AprResponse = await cacheFn(() => response.json(), aprsCache, 'aprs', 60)
return data.vaults.map((aprData) => {
const finalApr = aprData.apr.projected_apr * 100
return { address: aprData.address, apr: finalApr } as Apr
})
}
return []
} catch {
return []
}
}

View File

@ -3,15 +3,13 @@ import { getParamsQueryClient } from 'api/cosmwasm-client'
import { VaultConfigBaseForAddr } from 'types/generated/mars-params/MarsParams.types'
import iterateContractQuery from 'utils/iterateContractQuery'
export const getVaultConfigs = async (
chainConfig: ChainConfig,
): Promise<VaultConfigBaseForAddr[]> => {
export const getVaultConfigs = async (): Promise<VaultConfigBaseForAddr[]> => {
try {
const paramsQueryClient = await getParamsQueryClient(chainConfig)
const paramsQueryClient = await getParamsQueryClient()
return await cacheFn(
() => iterateContractQuery(paramsQueryClient.allVaultConfigs, 'addr'),
vaultConfigsCache,
`${chainConfig.id}/vaultConfigs`,
'vaultConfigs',
600,
)
} catch (ex) {

View File

@ -0,0 +1,21 @@
import { cacheFn, previewDepositCache } from 'api/cache'
import { getVaultQueryClient } from 'api/cosmwasm-client'
export async function getVaultTokenFromLp(
vaultAddress: string,
lpAmount: string,
): Promise<{ vaultAddress: string; amount: string }> {
try {
const client = await getVaultQueryClient(vaultAddress)
return cacheFn(
() =>
client.previewDeposit({ amount: lpAmount }).then((amount) => ({ vaultAddress, amount })),
previewDepositCache,
`vaults/${vaultAddress}/amounts/${lpAmount}`,
30,
)
} catch (ex) {
throw ex
}
}

View File

@ -1,13 +1,14 @@
import { cacheFn, vaultUtilizationCache } from 'api/cache'
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { VaultUtilizationResponse } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { VaultConfigBaseForString } from 'types/generated/mars-params/MarsParams.types'
export const getVaultUtilizations = async (
chainConfig: ChainConfig,
vaultConfigs: VaultConfigBaseForString[],
): Promise<VaultUtilizationResponse[]> => {
const creditManagerQueryClient = await getCreditManagerQueryClient(chainConfig)
if (!ENV.ADDRESS_PARAMS) return []
const creditManagerQueryClient = await getCreditManagerQueryClient()
try {
const vaultUtilizations$ = vaultConfigs.map((vaultConfig) => {
return cacheFn(

View File

@ -1,20 +1,28 @@
import getAssetParams from 'api/params/getAssetParams'
import getAprs from 'api/vaults/getVaultAprs'
import { getVaultConfigs } from 'api/vaults/getVaultConfigs'
import { getVaultUtilizations } from 'api/vaults/getVaultUtilizations'
import { ENV } from 'constants/env'
import { TESTNET_VAULTS_META_DATA, VAULTS_META_DATA } from 'constants/vaults'
import { NETWORK } from 'types/enums/network'
import { BN } from 'utils/helpers'
import { convertAprToApy } from 'utils/parsers'
import { resolveHLSStrategies } from 'utils/resolvers'
export default async function getVaults(chainConfig: ChainConfig): Promise<Vault[]> {
const assetParams = await getAssetParams(chainConfig)
const vaultConfigs = await getVaultConfigs(chainConfig)
const $vaultUtilizations = getVaultUtilizations(chainConfig, vaultConfigs)
const vaultMetaDatas = chainConfig.vaults
export default async function getVaults(): Promise<Vault[]> {
const assetParams = await getAssetParams()
const vaultConfigs = await getVaultConfigs()
const $vaultUtilizations = getVaultUtilizations(vaultConfigs)
const $aprs = getAprs()
const vaultMetaDatas =
ENV.NETWORK === NETWORK.TESTNET ? TESTNET_VAULTS_META_DATA : VAULTS_META_DATA
const HLSAssets = assetParams.filter((asset) => asset.credit_manager.hls)
const hlsStrategies = resolveHLSStrategies('vault', HLSAssets)
const vaults: Vault[] = []
await $vaultUtilizations.then((vaultUtilizations) => {
await Promise.all([$vaultUtilizations, $aprs]).then(([vaultUtilizations, aprs]) => {
return vaultConfigs.map((vaultConfig) => {
const apr = aprs.find((apr) => apr.address === vaultConfig.addr)
const vaultMetaData = vaultMetaDatas.find(
(vaultMetaData) => vaultMetaData.address === vaultConfig.addr,
)
@ -32,6 +40,8 @@ export default async function getVaults(chainConfig: ChainConfig): Promise<Vault
)?.utilization.amount || 0,
),
},
apy: apr ? convertAprToApy(apr.apr, 365) : null,
apr: apr ? apr.apr : null,
ltv: {
max: Number(vaultConfig.max_loan_to_value),
liq: Number(vaultConfig.liquidation_threshold),

View File

@ -2,13 +2,12 @@ import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ITEM_LIMIT_PER_QUERY } from 'constants/query'
export default async function getAccountIds(
chainConfig: ChainConfig,
address?: string,
previousResults?: AccountIdAndKind[],
): Promise<AccountIdAndKind[]> {
if (!address) return []
try {
const client = await getCreditManagerQueryClient(chainConfig)
const client = await getCreditManagerQueryClient()
const lastItem = previousResults && previousResults.at(-1)
const accounts = (
@ -25,7 +24,7 @@ export default async function getAccountIds(
return accumulated.sort((a, b) => parseInt(a.id) - parseInt(b.id))
}
return await getAccountIds(chainConfig, address, accumulated)
return await getAccountIds(address, accumulated)
} catch {
return new Promise((_, reject) => reject('No data'))
}

View File

@ -2,19 +2,16 @@ import getAccount from 'api/accounts/getAccount'
import getWalletAccountIds from 'api/wallets/getAccountIds'
import { AccountKind } from 'types/generated/mars-rover-health-computer/MarsRoverHealthComputer.types'
export default async function getAccounts(
kind: AccountKind,
chainConfig: ChainConfig,
address?: string,
): Promise<Account[]> {
export default async function getAccounts(kind: AccountKind, address?: string): Promise<Account[]> {
if (!address) return new Promise((_, reject) => reject('No address'))
const accountIdsAndKinds = await getWalletAccountIds(chainConfig, address)
const accountIdsAndKinds = await getWalletAccountIds(address)
const $accounts = accountIdsAndKinds
.filter((a) => a.kind === kind)
.map((account) => getAccount(chainConfig, account.id))
.map((account) => getAccount(account.id))
const accounts = await Promise.all($accounts).then((accounts) => accounts)
if (accounts) {
return accounts.sort((a, b) => Number(a.id) - Number(b.id))
}

View File

@ -0,0 +1,13 @@
import { getICNSQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { ChainInfoID } from 'types/enums/wallet'
export default async function getICNS(address?: string): Promise<ICNSResult | undefined> {
if (!address || ENV.CHAIN_ID !== ChainInfoID.Osmosis1) return
try {
const icnsQueryClient = await getICNSQueryClient()
return icnsQueryClient.primaryName({ address })
} catch (ex) {
throw ex
}
}

View File

@ -0,0 +1,14 @@
import { ENV } from 'constants/env'
export default async function getWalletBalances(address: string): Promise<Coin[]> {
const uri = '/cosmos/bank/v1beta1/balances/'
const response = await fetch(`${ENV.URL_REST}${uri}${address}`)
if (response.ok) {
const data = await response.json()
return data.balances
}
return new Promise((_, reject) => reject('No data'))
}

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'
import AccordionContent from 'components/common/AccordionContent'
import Card from 'components/common/Card'
import AccordionContent, { Item } from 'components/AccordionContent'
import Card from 'components/Card'
interface Props {
items: Item[]
@ -12,11 +12,11 @@ interface Props {
export default function Accordion(props: Props) {
if (props.allowMultipleOpen) {
return (
<>
<Card className='w-full'>
{props.items.map((item, index) => (
<AccordionContent key={index} item={item} index={index} />
))}
</>
</Card>
)
}

View File

@ -1,13 +1,21 @@
import classNames from 'classnames'
import { ChevronDown, ChevronRight } from 'components/common/Icons'
import Text from 'components/common/Text'
import { ChevronDown, ChevronRight } from 'components/Icons'
import Text from 'components/Text'
interface Props {
item: Item
index: number
}
export interface Item {
title: string
renderContent: () => React.ReactNode
isOpen?: boolean
renderSubTitle: () => React.ReactNode
toggleOpen: (index: number) => void
}
export default function AccordionContent(props: Props) {
const { title, renderContent, isOpen, renderSubTitle, toggleOpen } = props.item
@ -19,29 +27,20 @@ export default function AccordionContent(props: Props) {
<div
onClick={() => toggleOpen(props.index)}
className={classNames(
'mb-0 flex hover:cursor-pointer items-center justify-between bg-white/10 py-2 px-4 text-white border-b border-transparent',
'mb-0 flex hover:cursor-pointer items-center justify-between bg-white/10 p-4 text-white border-b border-transparent',
'[&::marker]:hidden [&::marker]:content-[""]',
isOpen && 'border-white/10',
isOpen && 'border-white/20',
)}
>
<div>
<Text size='sm' className='font-semibold'>
{title}
</Text>
<Text>{title}</Text>
{renderSubTitle()}
</div>
<div className='block pr-1 group-[[open]]/accordion:hidden'>
{isOpen ? <ChevronDown className='h-1.5' /> : <ChevronRight className='w-1.5' />}
</div>
</div>
<div
className={classNames(
'grid transition-[grid-template-rows]',
isOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
)}
>
<div className='overflow-hidden'>{renderContent()}</div>
</div>
{isOpen && <div className='bg-white/5 transition-[padding]'>{renderContent()}</div>}
</div>
)
}

View File

@ -0,0 +1,28 @@
import AssetRate from 'components/Asset/AssetRate'
import { byDenom } from 'utils/array'
export const APY_META = { accessorKey: 'apy', header: 'APY' }
interface Props {
apy: number
markets: Market[]
denom: string
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export default function Apr(props: Props) {
const { markets, type, denom, apy } = props
if (apy === 0) return <p className='w-full text-xs text-right number'>&ndash;</p>
const isEnabled = markets.find(byDenom(denom))?.borrowEnabled ?? false
return (
<AssetRate
className='justify-end text-xs'
rate={apy}
isEnabled={type !== 'lending' || isEnabled}
type='apy'
orientation='ltr'
/>
)
}

View File

@ -0,0 +1,22 @@
import Text from 'components/Text'
export const ASSET_META = { accessorKey: 'symbol', header: 'Asset', id: 'symbol' }
interface Props {
symbol: string
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export const borderColor = (type: Props['type']): string =>
type === 'borrowing' ? 'border-loss' : 'border-profit'
export default function Asset(props: Props) {
const { symbol, type } = props
return (
<Text size='xs'>
{symbol}
{type === 'borrowing' && <span className='ml-1 text-loss'>(debt)</span>}
{type === 'lending' && <span className='ml-1 text-profit'>(lent)</span>}
{type === 'vault' && <span className='ml-1 text-profit'>(farm)</span>}
</Text>
)
}

View File

@ -1,19 +1,18 @@
import { Row } from '@tanstack/react-table'
import classNames from 'classnames'
import { useMemo } from 'react'
import { getAmountChangeColor } from 'components/account/AccountBalancesTable/functions'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { getAmountChangeColor } from 'components/Account/AccountBalancesTable/functions'
import { FormattedNumber } from 'components/FormattedNumber'
import { MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math'
import { formatAmountToPrecision } from 'utils/formatters'
export const SIZE_META = { accessorKey: 'size', header: 'Size', meta: { className: 'w-40' } }
export const SIZE_META = { accessorKey: 'size', header: 'Size' }
interface Props {
size: number
amountChange: BigNumber
denom: string
type: PositionType
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export const sizeSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRow>): number => {
@ -28,9 +27,10 @@ export const sizeSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRo
export default function Size(props: Props) {
const { amountChange, type, size } = props
const color = useMemo(() => getAmountChangeColor(type, amountChange), [amountChange, type])
if (type === 'vault') return <p className='text-xs text-right number'>&ndash;</p>
const color = getAmountChangeColor(type, amountChange)
const className = classNames('text-xs text-right', color)
const allowZero = !amountChange.isZero()
@ -47,7 +47,6 @@ export default function Size(props: Props) {
const formattedAmount = formatAmountToPrecision(size, MAX_AMOUNT_DECIMALS)
const minimumAmount = allowZero ? 0 : MIN_AMOUNT
const lowAmount = formattedAmount === 0 ? minimumAmount : Math.max(formattedAmount, MIN_AMOUNT)
return (
<FormattedNumber
className={className}

View File

@ -1,8 +1,8 @@
import { Row } from '@tanstack/react-table'
import classNames from 'classnames'
import { getAmountChangeColor } from 'components/account/AccountBalancesTable/functions'
import DisplayCurrency from 'components/common/DisplayCurrency'
import { getAmountChangeColor } from 'components/Account/AccountBalancesTable/functions'
import DisplayCurrency from 'components/DisplayCurrency'
import { ORACLE_DENOM } from 'constants/oracle'
import { BNCoin } from 'types/classes/BNCoin'
import { BN } from 'utils/helpers'
@ -12,19 +12,10 @@ export const VALUE_META = { accessorKey: 'value', header: 'Value' }
interface Props {
amountChange: BigNumber
value: string
type: PositionType
type: 'deposits' | 'borrowing' | 'lending' | 'vault'
}
export const valueBalancesSortingFn = (
a: Row<AccountBalanceRow>,
b: Row<AccountBalanceRow>,
): number => {
const valueA = BN(a.original.value)
const valueB = BN(b.original.value)
return valueA.minus(valueB).toNumber()
}
export const valuePerpSortingFn = (a: Row<AccountPerpRow>, b: Row<AccountPerpRow>): number => {
export const valueSortingFn = (a: Row<AccountBalanceRow>, b: Row<AccountBalanceRow>): number => {
const valueA = BN(a.original.value)
const valueB = BN(b.original.value)
return valueA.minus(valueB).toNumber()
@ -33,12 +24,17 @@ export const valuePerpSortingFn = (a: Row<AccountPerpRow>, b: Row<AccountPerpRow
export default function Value(props: Props) {
const { amountChange, type, value } = props
const color = getAmountChangeColor(type, amountChange)
const allowZero = !amountChange.isZero()
const coin = new BNCoin({
denom: ORACLE_DENOM,
amount: value,
})
return (
<DisplayCurrency coin={coin} className={classNames('text-xs text-right', color)} showZero />
<DisplayCurrency
coin={coin}
className={classNames('text-xs text-right', color)}
showZero={!allowZero}
/>
)
}

View File

@ -0,0 +1,62 @@
import { ColumnDef } from '@tanstack/react-table'
import { useMemo } from 'react'
import Apy, { APY_META } from 'components/Account/AccountBalancesTable/Columns/Apy'
import Asset, { ASSET_META } from 'components/Account/AccountBalancesTable/Columns/Asset'
import Size, {
SIZE_META,
sizeSortingFn,
} from 'components/Account/AccountBalancesTable/Columns/Size'
import Value, {
VALUE_META,
valueSortingFn,
} from 'components/Account/AccountBalancesTable/Columns/Value'
import useMarketAssets from 'hooks/useMarketAssets'
export default function useAccountBalancesColumns() {
const { data: markets } = useMarketAssets()
return useMemo<ColumnDef<AccountBalanceRow>[]>(() => {
return [
{
...ASSET_META,
cell: ({ row }) => <Asset type={row.original.type} symbol={row.original.symbol} />,
},
{
...VALUE_META,
cell: ({ row }) => (
<Value
amountChange={row.original.amountChange}
value={row.original.value}
type={row.original.type}
/>
),
sortingFn: valueSortingFn,
},
{
...SIZE_META,
cell: ({ row }) => (
<Size
size={row.original.size}
amountChange={row.original.amountChange}
denom={row.original.denom}
type={row.original.type}
/>
),
sortingFn: sizeSortingFn,
},
{
...APY_META,
cell: ({ row }) => (
<Apy
apy={row.original.apy}
markets={markets}
denom={row.original.denom}
type={row.original.type}
/>
),
},
]
}, [markets])
}

View File

@ -0,0 +1,71 @@
import { BN_ZERO } from 'constants/math'
import { BNCoin } from 'types/classes/BNCoin'
import { demagnify, getCoinValue } from 'utils/formatters'
export function getAssetAccountBalanceRow(
type: 'deposits' | 'borrowing' | 'lending',
asset: Asset,
prices: BNCoin[],
position: BNCoin,
apy: number,
prev?: BNCoin,
): AccountBalanceRow {
const { amount, denom } = position
const amountChange = !prev ? position.amount : position.amount.minus(prev.amount)
return {
type,
symbol: asset.symbol,
size: demagnify(amount, asset),
value: getCoinValue(BNCoin.fromDenomAndBigNumber(denom, amount), prices).toString(),
denom,
amount,
apy,
amountChange,
}
}
export function getVaultAccountBalanceRow(
vault: DepositedVault,
apy: number,
prev?: DepositedVault,
): AccountBalanceRow {
const { name } = vault
const previous = prev || vault
const totalLockedValue = vault.values.primary.plus(vault.values.secondary)
const totalValue = totalLockedValue.plus(vault.values.unlocked).plus(vault.values.unlocking)
const prevTotalValue = previous.values.primary
.plus(previous.values.secondary)
.plus(previous.values.unlocked)
.plus(previous.values.unlocking)
const amountChange = !prev ? totalValue : totalValue.minus(prevTotalValue)
if (totalLockedValue.isLessThan(totalValue)) {
apy = totalLockedValue.dividedBy(totalValue).times(apy).toNumber()
}
return {
type: 'vault',
symbol: name,
size: 0,
value: totalValue.toString(),
denom: vault.denoms.lp,
amount: BN_ZERO,
apy,
amountChange,
}
}
export function getAmountChangeColor(
type: 'deposits' | 'borrowing' | 'lending' | 'vault',
amount: BigNumber,
) {
if (type === 'borrowing') {
if (amount.isGreaterThan(0)) return 'text-loss'
if (amount.isLessThan(0)) return 'text-profit'
}
if (amount.isGreaterThan(0)) return 'text-profit'
if (amount.isLessThan(0)) return 'text-loss'
return ''
}

View File

@ -1,14 +1,14 @@
import classNames from 'classnames'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'
import useAccountBalancesColumns from 'components/account/AccountBalancesTable/Columns/useAccountBalancesColumns'
import useAccountBalanceData from 'components/account/AccountBalancesTable/useAccountBalanceData'
import AccountFundFullPage from 'components/account/AccountFund/AccountFundFullPage'
import ActionButton from 'components/common/Button/ActionButton'
import Card from 'components/common/Card'
import Table from 'components/common/Table'
import useAccountBalancesColumns from 'components/Account/AccountBalancesTable/Columns/useAccountBalancesColumns'
import useAccountBalanceData from 'components/Account/AccountBalancesTable/useAccountBalanceData'
import AccountFundFullPage from 'components/Account/AccountFund/AccountFundFullPage'
import ActionButton from 'components/Button/ActionButton'
import Card from 'components/Card'
import Table from 'components/Table'
import ConditionalWrapper from 'hocs/ConditionalWrapper'
import useCurrentAccount from 'hooks/accounts/useCurrentAccount'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useStore from 'store'
import { getPage, getRoute } from 'utils/route'
@ -19,19 +19,10 @@ interface Props {
borrowingData: BorrowMarketTableData[]
hideCard?: boolean
tableBodyClassName?: string
showLiquidationPrice?: boolean
}
export default function AccountBalancesTable(props: Props) {
const [searchParams] = useSearchParams()
const {
account,
lendingData,
borrowingData,
tableBodyClassName,
hideCard,
showLiquidationPrice,
} = props
const { account, lendingData, borrowingData, tableBodyClassName, hideCard } = props
const currentAccount = useCurrentAccount()
const navigate = useNavigate()
const { pathname } = useLocation()
@ -45,7 +36,7 @@ export default function AccountBalancesTable(props: Props) {
isHls: props.isHls,
})
const columns = useAccountBalancesColumns(account, showLiquidationPrice)
const columns = useAccountBalancesColumns()
if (accountBalanceData.length === 0)
return (
@ -64,7 +55,7 @@ export default function AccountBalancesTable(props: Props) {
color='tertiary'
onClick={() => {
if (currentAccount?.id !== account.id) {
navigate(getRoute(getPage(pathname), searchParams, address, account.id))
navigate(getRoute(getPage(pathname), address, account.id))
}
useStore.setState({
focusComponent: {
@ -89,7 +80,7 @@ export default function AccountBalancesTable(props: Props) {
initialSorting={[]}
spacingClassName='p-2'
hideCard={hideCard}
type='balances'
isBalancesTable
/>
)
}

View File

@ -1,10 +1,15 @@
import { useMemo } from 'react'
import { getAssetAccountBalanceRow } from 'components/account/AccountBalancesTable/functions'
import useAllAssets from 'hooks/assets/useAllAssets'
import {
getAssetAccountBalanceRow,
getVaultAccountBalanceRow,
} from 'components/Account/AccountBalancesTable/functions'
import { ASSETS } from 'constants/assets'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import usePrices from 'hooks/usePrices'
import { byDenom } from 'utils/array'
import { convertLiquidityRateToAPR } from 'utils/formatters'
import { convertAprToApy } from 'utils/parsers'
interface Props {
account: Account
@ -16,57 +21,57 @@ interface Props {
export default function useAccountBalanceData(props: Props) {
const { account, updatedAccount, lendingData, borrowingData } = props
const { data: hlsStrategies } = useHLSStakingAssets()
const { data: prices } = usePrices()
const assets = useAllAssets()
return useMemo<AccountBalanceRow[]>(() => {
const usedAccount = updatedAccount ?? account
const accountDeposits = usedAccount?.deposits ?? []
const accountLends = usedAccount?.lends ?? []
const accountDebts = usedAccount?.debts ?? []
const accountVaults = usedAccount?.vaults ?? []
const deposits: AccountBalanceRow[] = []
accountDeposits.forEach((deposit) => {
const asset = assets.find(byDenom(deposit.denom))
const asset = ASSETS.find(byDenom(deposit.denom))
if (!asset) return
const apy = props.isHls
? hlsStrategies.find((strategy) => strategy.denoms.deposit === asset.denom)?.apy ?? 0
: 0
const prevDeposit = updatedAccount ? account?.deposits.find(byDenom(deposit.denom)) : deposit
deposits.push(
getAssetAccountBalanceRow('deposit', asset, prices, assets, deposit, apy, prevDeposit),
)
deposits.push(getAssetAccountBalanceRow('deposits', asset, prices, deposit, apy, prevDeposit))
})
const lends = accountLends.map((lending) => {
const asset = assets.find(byDenom(lending.denom)) ?? assets[0]
const apy =
lendingData.find((market) => market.asset.denom === lending.denom)?.apy.deposit ?? 0
const asset = ASSETS.find(byDenom(lending.denom)) ?? ASSETS[0]
const apr = convertLiquidityRateToAPR(
lendingData.find((market) => market.asset.denom === lending.denom)?.marketLiquidityRate ??
0,
)
const apy = convertAprToApy(apr, 365)
const prevLending = updatedAccount
? account?.lends.find((position) => position.denom === lending.denom)
: lending
return getAssetAccountBalanceRow('lend', asset, prices, assets, lending, apy, prevLending)
return getAssetAccountBalanceRow('lending', asset, prices, lending, apy, prevLending)
})
const vaults = accountVaults.map((vault) => {
const apy = vault.apy ?? 0
const prevVault = updatedAccount
? account?.vaults.find((position) => position.name === vault.name)
: vault
return getVaultAccountBalanceRow(vault, apy, prevVault)
})
const debts = accountDebts.map((debt) => {
const asset = assets.find(byDenom(debt.denom)) ?? assets[0]
const apy = borrowingData.find((market) => market.asset.denom === debt.denom)?.apy.borrow ?? 0
const asset = ASSETS.find(byDenom(debt.denom)) ?? ASSETS[0]
const apy = borrowingData.find((market) => market.asset.denom === debt.denom)?.borrowRate ?? 0
const prevDebt = updatedAccount
? account?.debts.find((position) => position.denom === debt.denom)
: debt
return getAssetAccountBalanceRow('borrow', asset, prices, assets, debt, apy, prevDebt)
return getAssetAccountBalanceRow('borrowing', asset, prices, debt, apy, prevDebt)
})
return [...deposits, ...lends, ...debts]
}, [
updatedAccount,
account,
props.isHls,
hlsStrategies,
prices,
assets,
lendingData,
borrowingData,
])
return [...deposits, ...lends, ...vaults, ...debts]
}, [updatedAccount, account, props.isHls, hlsStrategies, prices, lendingData, borrowingData])
}

View File

@ -2,21 +2,23 @@ import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import { useMemo } from 'react'
import useBorrowMarketAssetsTableData from 'components/borrow/Table/useBorrowMarketAssetsTableData'
import DisplayCurrency from 'components/common/DisplayCurrency'
import { FormattedNumber } from 'components/common/FormattedNumber'
import { ArrowRight } from 'components/common/Icons'
import Text from 'components/common/Text'
import useLendingMarketAssetsTableData from 'components/earn/lend/Table/useLendingMarketAssetsTableData'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons'
import Text from 'components/Text'
import { BN_ZERO, MAX_AMOUNT_DECIMALS } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle'
import useAllAssets from 'hooks/assets/useAllAssets'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHLSStakingAssets from 'hooks/useHLSStakingAssets'
import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableData'
import usePrices from 'hooks/usePrices'
import useVaultAprs from 'hooks/vaults/useVaultAprs'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountApr, getAccountPositionValues } from 'utils/accounts'
import {
calculateAccountApr,
calculateAccountBalanceValue,
getAccountPositionValues,
} from 'utils/accounts'
interface Props {
account: Account
@ -38,9 +40,7 @@ export default function AccountComposition(props: Props) {
const hasChanged = !!updatedAccount
const { data: prices } = usePrices()
const { data: hlsStrategies } = useHLSStakingAssets()
const { data: vaultAprs } = useVaultAprs()
const assets = useAllAssets()
const data = useBorrowMarketAssetsTableData()
const { data } = useBorrowMarketAssetsTableData(false)
const borrowAssetsData = useMemo(() => data?.allAssets || [], [data])
const { availableAssets: lendingAvailableAssets, accountLentAssets } =
useLendingMarketAssetsTableData()
@ -50,15 +50,15 @@ export default function AccountComposition(props: Props) {
)
const [depositsBalance, lendsBalance, debtsBalance, vaultsBalance] = useMemo(
() => getAccountPositionValues(account, prices, assets),
[account, assets, prices],
() => getAccountPositionValues(account, prices),
[account, prices],
)
const totalBalance = depositsBalance.plus(lendsBalance).plus(vaultsBalance)
const [updatedPositionValue, updatedDebtsBalance] = useMemo(() => {
const [updatedDepositsBalance, updatedLendsBalance, updatedDebtsBalance, updatedVaultsBalance] =
updatedAccount
? getAccountPositionValues(updatedAccount, prices, assets)
? getAccountPositionValues(updatedAccount, prices)
: [BN_ZERO, BN_ZERO, BN_ZERO]
const updatedPositionValue = updatedDepositsBalance
@ -66,7 +66,13 @@ export default function AccountComposition(props: Props) {
.plus(updatedVaultsBalance)
return [updatedPositionValue, updatedDebtsBalance]
}, [updatedAccount, prices, assets])
}, [updatedAccount, prices])
const netWorth = useMemo(() => calculateAccountBalanceValue(account, prices), [account, prices])
const updatedTotalBalance = useMemo(
() => (updatedAccount ? calculateAccountBalanceValue(updatedAccount, prices) : BN_ZERO),
[updatedAccount, prices],
)
const apr = useMemo(
() =>
@ -76,20 +82,9 @@ export default function AccountComposition(props: Props) {
lendingAssetsData,
prices,
hlsStrategies,
assets,
vaultAprs,
props.isHls,
),
[
account,
assets,
borrowAssetsData,
hlsStrategies,
lendingAssetsData,
prices,
props.isHls,
vaultAprs,
],
[account, borrowAssetsData, hlsStrategies, lendingAssetsData, prices, props.isHls],
)
const updatedApr = useMemo(
() =>
@ -100,21 +95,10 @@ export default function AccountComposition(props: Props) {
lendingAssetsData,
prices,
hlsStrategies,
assets,
vaultAprs,
props.isHls,
)
: BN_ZERO,
[
updatedAccount,
borrowAssetsData,
lendingAssetsData,
prices,
hlsStrategies,
assets,
vaultAprs,
props.isHls,
],
[updatedAccount, borrowAssetsData, lendingAssetsData, prices, hlsStrategies, props.isHls],
)
return (
@ -132,11 +116,17 @@ export default function AccountComposition(props: Props) {
className='pb-3'
isDecrease
/>
<Item
title='Net worth'
current={netWorth}
change={hasChanged ? updatedTotalBalance : netWorth}
className='py-3 font-bold border border-transparent border-y-white/20'
/>
<Item
title='APR'
current={apr}
change={hasChanged ? updatedApr : apr}
className='pb-3'
className='py-3'
isPercentage
/>
</div>
@ -162,7 +152,6 @@ function Item(props: ItemProps) {
suffix: '%',
minDecimals: 2,
maxDecimals: current.abs().isLessThan(0.1) ? MAX_AMOUNT_DECIMALS : 2,
abbreviated: false,
}}
className='text-sm'
animate
@ -171,7 +160,6 @@ function Item(props: ItemProps) {
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, current)}
className='text-sm'
options={{ abbreviated: false }}
/>
)}
{current.toFixed(2) !== change.toFixed(2) && (
@ -194,7 +182,6 @@ function Item(props: ItemProps) {
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, change)}
className={classNames('text-sm', increase ? 'text-profit' : 'text-loss')}
options={{ abbreviated: false }}
/>
)}
</>

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