diff --git a/src/pages/_borrow/BorrowModal/BorrowModal.tsx b/src/pages/_borrow/BorrowModal/BorrowModal.tsx new file mode 100644 index 0000000..604dc48 --- /dev/null +++ b/src/pages/_borrow/BorrowModal/BorrowModal.tsx @@ -0,0 +1,58 @@ +import type { CurrencyBinding } from 'src/currency-bindings'; +import { useWallet } from 'src/stellar-wallet'; +import { BorrowStep } from './BorrowStep'; +import { TrustLineStep } from './TrustlineStep'; + +export interface BorrowModalProps { + modalId: string; + onClose: () => void; + currency: CurrencyBinding; + totalSupplied: bigint; +} + +export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: BorrowModalProps) => { + const { name, ticker } = currency; + const { wallet, walletBalances, signTransaction, refetchBalances, prices } = useWallet(); + + const closeModal = () => { + refetchBalances(); + onClose(); + }; + + // Modal is impossible to open before the wallet is loaded. + if (!wallet || !walletBalances || !prices) return null; + + const isTrustline = walletBalances[ticker].trustLine; + + return ( + +
+

Borrow {name}

+ {!isTrustline ? ( + + ) : ( + + )} +
+ {/* Invisible backdrop that closes the modal on click */} +
+ +
+
+ ); +}; diff --git a/src/pages/_borrow/BorrowModal.tsx b/src/pages/_borrow/BorrowModal/BorrowStep.tsx similarity index 58% rename from src/pages/_borrow/BorrowModal.tsx rename to src/pages/_borrow/BorrowModal/BorrowStep.tsx index 8249a19..07b74ce 100644 --- a/src/pages/_borrow/BorrowModal.tsx +++ b/src/pages/_borrow/BorrowModal/BorrowStep.tsx @@ -1,32 +1,31 @@ -import { Warning } from '@components/Alert'; import { Button } from '@components/Button'; import { CryptoAmountSelector } from '@components/CryptoAmountSelector'; import { Loading } from '@components/Loading'; import { contractClient as loanManagerClient } from '@contracts/loan_manager'; import { getIntegerPart, to7decimals } from '@lib/converters'; import { SCALAR_7, fromCents, toCents } from '@lib/formatting'; -import { createAddTrustlineTransaction, sendTransaction } from '@lib/horizon'; import type { SupportedCurrency } from 'currencies'; import { type ChangeEvent, useState } from 'react'; import { CURRENCY_BINDINGS, CURRENCY_BINDINGS_ARR, type CurrencyBinding } from 'src/currency-bindings'; -import { useWallet } from 'src/stellar-wallet'; +import { type BalanceRecord, type PriceRecord, type Wallet, useWallet } from 'src/stellar-wallet'; +const HEALTH_FACTOR_AUTO_THRESHOLD = 1.65; const HEALTH_FACTOR_MIN_THRESHOLD = 1.2; const HEALTH_FACTOR_GOOD_THRESHOLD = 1.6; const HEALTH_FACTOR_EXCELLENT_THRESHOLD = 2.0; -const HEALTH_FACTOR_AUTO_THRESHOLD = 1.65; - -export interface BorrowModalProps { - modalId: string; +export interface BorrowStepProps { onClose: () => void; currency: CurrencyBinding; totalSupplied: bigint; + wallet: Wallet; + walletBalances: BalanceRecord; + prices: PriceRecord; } -export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: BorrowModalProps) => { +export const BorrowStep = ({ onClose, currency, totalSupplied, wallet, walletBalances, prices }: BorrowStepProps) => { const { name, ticker, contractId: loanCurrencyId } = currency; - const { wallet, walletBalances, signTransaction, refetchBalances, prices } = useWallet(); + const { signTransaction } = useWallet(); const [isBorrowing, setIsBorrowing] = useState(false); const [loanAmount, setLoanAmount] = useState('0'); @@ -37,14 +36,11 @@ export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: Borro ({ ticker }) => ticker, ); - // The modal is impossible to open without a wallet or before the balances load. - if (!wallet || !walletBalances) return null; - const loanBalance = walletBalances[ticker]; const collateralBalance = walletBalances[collateralTicker]; - const loanPrice = prices?.[ticker]; - const collateralPrice = prices?.[collateralTicker]; + const loanPrice = prices[ticker]; + const collateralPrice = prices[collateralTicker]; const loanAmountCents = loanPrice ? toCents(loanPrice, BigInt(loanAmount) * SCALAR_7) : undefined; const collateralAmountCents = collateralPrice @@ -54,22 +50,15 @@ export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: Borro const healthFactor = loanAmountCents && loanAmountCents > 0n ? Number(collateralAmountCents) / Number(loanAmountCents) : 0; - // The modal is impossible to open without collateral balance. - if (!collateralBalance.trustLine) return null; + // TODO: get this from the contract. + const interestRate = '7.5%'; - const closeModal = () => { - refetchBalances(); + const handleCancel = () => { setLoanAmount('0'); setCollateralAmount('0'); onClose(); }; - const handleAddTrustlineClick = async () => { - const tx = await createAddTrustlineTransaction(wallet.address, currency); - const signedTx = await signTransaction(tx.toXDR()); - sendTransaction(signedTx); - }; - const handleBorrowClick = async () => { if (!wallet) { alert('Please connect your wallet first!'); @@ -96,7 +85,7 @@ export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: Borro }); await tx.signAndSend({ signTransaction }); alert('Loan created succesfully!'); - closeModal(); + onClose(); } catch (err) { console.error('Error borrowing', err); alert('Error borrowing'); @@ -137,90 +126,72 @@ export const BorrowModal = ({ modalId, onClose, currency, totalSupplied }: Borro const maxLoan = (totalSupplied / 10_000_000n).toString(); - const maxCollateral = getIntegerPart(collateralBalance.balanceLine.balance); + const maxCollateral = getIntegerPart(collateralBalance.trustLine ? collateralBalance.balanceLine.balance : '0'); const handleSelectMaxLoan = () => setLoanAmount(maxLoan); const handleSelectMaxCollateral = () => setCollateralAmount(maxCollateral); - // TODO: get this from the contract. - const interestRate = '7.5%'; - return ( - -
-

Borrow {name}

- {!isTrustline ? ( - - Create a trustline for {ticker} in your wallet first! - - - ) : null} -

- Borrow {name} using another asset as a collateral. The value of the collateral must exceed the value of the - borrowed asset. You will receive the collateral back to your wallet after repaying the loan in full. -

-

- The higher the value of the collateral is to the value of the borrowed asset, the safer this loan is. This is - visualised by the health factor. -

-

- The loan will be available for liquidation if the value of the borrowed asset raises to the value of the - collateral, causing you to lose some of your collateral. -

-

The interest rate changes as the amount of assets borrowed from the pools changes.

-

The annual interest rate is currently {interestRate}.

- -

Amount to borrow

- - -

Amount of collateral

- - -

Health Factor

- - -
- + {!isBorrowing ? ( + + ) : ( + - {!isBorrowing ? ( - - ) : ( - - )} -
+ )}
- {/* Invisible backdrop that closes the modal on click */} -
- -
-
+ ); }; diff --git a/src/pages/_borrow/BorrowModal/TrustlineStep.tsx b/src/pages/_borrow/BorrowModal/TrustlineStep.tsx new file mode 100644 index 0000000..493e765 --- /dev/null +++ b/src/pages/_borrow/BorrowModal/TrustlineStep.tsx @@ -0,0 +1,56 @@ +import { Button } from '@components/Button'; +import { Loading } from '@components/Loading'; +import { createAddTrustlineTransaction, sendTransaction } from '@lib/horizon'; +import { useState } from 'react'; +import type { CurrencyBinding } from 'src/currency-bindings'; +import type { SignTransaction, Wallet } from 'src/stellar-wallet'; + +export interface TrustLineStepProps { + onClose: () => void; + currency: CurrencyBinding; + wallet: Wallet; + signTransaction: SignTransaction; + refetchBalances: () => void; +} + +export const TrustLineStep = ({ onClose, currency, wallet, signTransaction, refetchBalances }: TrustLineStepProps) => { + const { name, ticker } = currency; + + const [isCreating, setIsCreating] = useState(false); + + const handleAddTrustlineClick = async () => { + try { + setIsCreating(true); + const tx = await createAddTrustlineTransaction(wallet.address, currency); + const signedTx = await signTransaction(tx.toXDR()); + await sendTransaction(signedTx); + alert(`Succesfully created a trustline for ${ticker}!`); + } catch (err) { + alert('Error creating a trustline :('); + console.error('Error creating trustline:', err); + } + refetchBalances(); + setIsCreating(false); + }; + + return ( + <> +

+ You don't have a trustline for {name} in your wallet. Laina cannot transfer you the tokens without a trustline. +

+
+ + {!isCreating ? ( + + ) : ( + + )} +
+ + ); +}; diff --git a/src/pages/_borrow/BorrowableAsset.tsx b/src/pages/_borrow/BorrowableAsset.tsx index 234d93b..69bc644 100644 --- a/src/pages/_borrow/BorrowableAsset.tsx +++ b/src/pages/_borrow/BorrowableAsset.tsx @@ -7,7 +7,7 @@ import { contractClient as loanManagerClient } from '@contracts/loan_manager'; import { isBalanceZero } from '@lib/converters'; import type { CurrencyBinding } from 'src/currency-bindings'; import { useWallet } from 'src/stellar-wallet'; -import { BorrowModal } from './BorrowModal'; +import { BorrowModal } from './BorrowModal/BorrowModal'; interface BorrowableAssetCardProps { currency: CurrencyBinding; diff --git a/src/pages/_lend/DepositModal.tsx b/src/pages/_lend/DepositModal.tsx index 3d96582..19bb3bd 100644 --- a/src/pages/_lend/DepositModal.tsx +++ b/src/pages/_lend/DepositModal.tsx @@ -25,7 +25,7 @@ export const DepositModal = ({ modalId, onClose, currency }: DepositModalProps) const amountCents = price ? toCents(price, BigInt(amount) * SCALAR_7) : undefined; - if (!balance || !balance.trustline) return null; + if (!balance || !balance.trustLine) return null; const max = getIntegerPart(balance.balanceLine.balance); diff --git a/src/pages/_lend/LendableAsset.tsx b/src/pages/_lend/LendableAsset.tsx index fd80c3b..eb1a05f 100644 --- a/src/pages/_lend/LendableAsset.tsx +++ b/src/pages/_lend/LendableAsset.tsx @@ -24,7 +24,7 @@ export const LendableAsset = ({ currency }: LendableAssetProps) => { const balance: Balance | undefined = walletBalances?.[ticker]; - const isPoor = !balance?.trustline || isBalanceZero(balance.balanceLine.balance); + const isPoor = !balance?.trustLine || isBalanceZero(balance.balanceLine.balance); const fetchAvailableContractBalance = useCallback(async () => { if (!contractClient) return; diff --git a/src/stellar-wallet.tsx b/src/stellar-wallet.tsx index 9194286..c50a05c 100644 --- a/src/stellar-wallet.tsx +++ b/src/stellar-wallet.tsx @@ -44,7 +44,7 @@ export type WalletContext = { signTransaction: SignTransaction; }; -type SignTransaction = ( +export type SignTransaction = ( tx: XDR_BASE64, opts?: { network?: string;