diff --git a/v3/components/BorrowModal/BorrowModal.tsx b/v3/components/BorrowModal/BorrowModal.tsx
new file mode 100644
index 000000000..ac92c3455
--- /dev/null
+++ b/v3/components/BorrowModal/BorrowModal.tsx
@@ -0,0 +1,156 @@
+import {
+ Box,
+ Button,
+ Flex,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+ Spinner,
+ Text,
+} from '@chakra-ui/react';
+import { Amount } from '@snx-v3/Amount';
+import Wei from '@synthetixio/wei';
+import { TransactionStatus } from '@snx-v3/txnReducer';
+import { CheckIcon, CloseIcon } from '@snx-v3/Multistep';
+import { PropsWithChildren, useContext } from 'react';
+import { useParams } from '@snx-v3/useParams';
+import { ManagePositionContext } from '@snx-v3/ManagePositionContext';
+import { useCollateralType } from '@snx-v3/useCollateralTypes';
+import { useBorrow } from '../../lib/useBorrow';
+
+function StepIcon({ txnStatus, children }: PropsWithChildren<{ txnStatus: TransactionStatus }>) {
+ switch (txnStatus) {
+ case 'error':
+ return ;
+ case 'success':
+ return ;
+ case 'prompting':
+ case 'pending':
+ return ;
+ default:
+ return (
+
+ {children}
+
+ );
+ }
+}
+
+const statusColor = (txnStatus: TransactionStatus) => {
+ if (txnStatus === 'error' || txnStatus === 'success') return txnStatus;
+ return 'gray.700';
+};
+export const BorrowModalUi: React.FC<{
+ onClose: () => void;
+ debtChange: Wei;
+ isOpen: boolean;
+ txnStatus: TransactionStatus;
+ execBorrow: () => void;
+}> = ({ onClose, isOpen, debtChange, txnStatus, execBorrow }) => {
+ return (
+
+
+
+ Complete this action
+
+
+
+
+ 1
+
+
+ Borrow
+
+
+
+
+
+
+ );
+};
+
+export const BorrowModal: React.FC<{
+ onClose: () => void;
+ isOpen: boolean;
+}> = ({ onClose, isOpen }) => {
+ const { debtChange } = useContext(ManagePositionContext);
+ const params = useParams();
+ const collateralType = useCollateralType(params.collateralSymbol);
+ const {
+ exec: execBorrow,
+ txnState,
+ settle: settleBorrow,
+ } = useBorrow({
+ accountId: params.accountId,
+ poolId: params.poolId,
+ collateralTypeAddress: collateralType?.tokenAddress,
+ debtChange,
+ });
+ const { txnStatus } = txnState;
+ if (!params.poolId || !params.accountId || !collateralType) return null;
+ return (
+ {
+ settleBorrow();
+ onClose();
+ }}
+ isOpen={isOpen}
+ />
+ );
+};
diff --git a/v3/components/BorrowModal/index.ts b/v3/components/BorrowModal/index.ts
new file mode 100644
index 000000000..a6ffcffb1
--- /dev/null
+++ b/v3/components/BorrowModal/index.ts
@@ -0,0 +1 @@
+export * from './BorrowModal';
diff --git a/v3/components/BorrowModal/package.json b/v3/components/BorrowModal/package.json
new file mode 100644
index 000000000..d7b00b4c7
--- /dev/null
+++ b/v3/components/BorrowModal/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@snx-v3/BorrowModal",
+ "private": true,
+ "main": "index.ts",
+ "version": "0.0.1",
+ "dependencies": {
+ "@chakra-ui/react": "^2.2.8",
+ "@snx-v3/Amount": "workspace:*",
+ "@snx-v3/ManagePositionContext": "workspace:*",
+ "@snx-v3/Multistep": "workspace:*",
+ "@snx-v3/txnReducer": "workspace:*",
+ "@snx-v3/useCollateralTypes": "workspace:*",
+ "@snx-v3/useParams": "workspace:*",
+ "@synthetixio/wei": "workspace:*",
+ "react": "^18.2.0"
+ }
+}
diff --git a/v3/lib/useBorrow/index.ts b/v3/lib/useBorrow/index.ts
new file mode 100644
index 000000000..dad6f3218
--- /dev/null
+++ b/v3/lib/useBorrow/index.ts
@@ -0,0 +1 @@
+export * from './useBorrow';
diff --git a/v3/lib/useBorrow/package.json b/v3/lib/useBorrow/package.json
new file mode 100644
index 000000000..1f68de079
--- /dev/null
+++ b/v3/lib/useBorrow/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@snx-v3/useBorrow",
+ "private": true,
+ "main": "index.ts",
+ "version": "0.0.1",
+ "dependencies": {
+ "@snx-v3/txnReducer": "workspace:*",
+ "@snx-v3/useBlockchain": "workspace:*",
+ "@snx-v3/useCoreProxy": "workspace:*",
+ "@snx-v3/useGasOptions": "workspace:*",
+ "@snx-v3/useGasPrice": "workspace:*",
+ "@snx-v3/useGasSpeed": "workspace:*",
+ "@synthetixio/wei": "workspace:*",
+ "@tanstack/react-query": "^4.3.4",
+ "ethers": "^5.7.2",
+ "react": "^18.2.0"
+ }
+}
diff --git a/v3/lib/useBorrow/useBorrow.tsx b/v3/lib/useBorrow/useBorrow.tsx
new file mode 100644
index 000000000..0b308e955
--- /dev/null
+++ b/v3/lib/useBorrow/useBorrow.tsx
@@ -0,0 +1,106 @@
+import { useReducer } from 'react';
+import { useCoreProxy, CoreProxyContractType } from '@snx-v3/useCoreProxy';
+import { formatGasPriceForTransaction } from '@snx-v3/useGasOptions';
+import { useMutation } from '@tanstack/react-query';
+import { useNetwork, useSigner } from '@snx-v3/useBlockchain';
+import { initialState, reducer } from '@snx-v3/txnReducer';
+import Wei from '@synthetixio/wei';
+import { BigNumber } from 'ethers';
+import { getGasPrice } from '@snx-v3/useGasPrice';
+import { useGasSpeed } from '@snx-v3/useGasSpeed';
+
+const createPopulateTransaction = ({
+ CoreProxy,
+ accountId,
+ poolId,
+ collateralTypeAddress,
+ debtChange,
+}: {
+ CoreProxy?: CoreProxyContractType;
+ accountId?: string;
+ poolId?: string;
+ collateralTypeAddress?: string;
+ debtChange: Wei;
+}) => {
+ if (!(CoreProxy && poolId && accountId && collateralTypeAddress)) return;
+ if (debtChange.eq(0)) return;
+
+ return () =>
+ CoreProxy.populateTransaction.mintUsd(
+ BigNumber.from(accountId),
+ BigNumber.from(poolId),
+ collateralTypeAddress,
+ debtChange.toBN(),
+ {
+ gasLimit: CoreProxy.estimateGas.mintUsd(
+ BigNumber.from(accountId),
+ BigNumber.from(poolId),
+ collateralTypeAddress,
+ debtChange.toBN()
+ ),
+ }
+ );
+};
+export const useBorrow = (
+ {
+ accountId,
+ poolId,
+ collateralTypeAddress,
+ debtChange,
+ }: {
+ accountId?: string;
+ poolId?: string;
+ collateralTypeAddress?: string;
+ debtChange: Wei;
+ },
+ eventHandlers?: { onSuccess?: () => void; onMutate?: () => void; onError?: (e: Error) => void }
+) => {
+ const [txnState, dispatch] = useReducer(reducer, initialState);
+ const { gasSpeed } = useGasSpeed();
+ const { data: CoreProxy } = useCoreProxy();
+ const populateTransaction = createPopulateTransaction({
+ CoreProxy,
+ accountId,
+ poolId,
+ collateralTypeAddress,
+ debtChange,
+ });
+ const signer = useSigner();
+ const { name: networkName, id: networkId } = useNetwork();
+
+ const mutation = useMutation(async () => {
+ if (!signer || !populateTransaction) return;
+ try {
+ dispatch({ type: 'prompting' });
+
+ const [populatedTxn, gasPrices] = await Promise.all([
+ populateTransaction(),
+ getGasPrice({ networkId, networkName }),
+ ]);
+ const gasLimit = populatedTxn.gasLimit || BigNumber.from(0);
+ const gasOptionsForTransaction = formatGasPriceForTransaction({
+ gasLimit,
+ gasPrices,
+ gasSpeed,
+ });
+ const txn = await signer.sendTransaction({
+ ...populatedTxn,
+ ...gasOptionsForTransaction,
+ });
+ dispatch({ type: 'pending', payload: { txnHash: txn.hash } });
+
+ await txn.wait();
+ dispatch({ type: 'success' });
+ } catch (error: any) {
+ dispatch({ type: 'error', payload: { error } });
+ throw error;
+ }
+ }, eventHandlers);
+ return {
+ mutation,
+ txnState,
+ settle: () => dispatch({ type: 'settled' }),
+ isLoading: mutation.isLoading,
+ exec: mutation.mutateAsync,
+ };
+};
diff --git a/v3/ui/package.json b/v3/ui/package.json
index c72e69921..c2172e89b 100644
--- a/v3/ui/package.json
+++ b/v3/ui/package.json
@@ -26,6 +26,7 @@
"@snx-v3/Amount": "workspace:*",
"@snx-v3/Balance": "workspace:*",
"@snx-v3/BorderBox": "workspace:*",
+ "@snx-v3/BorrowModal": "workspace:*",
"@snx-v3/CollateralTypeSelector": "workspace:*",
"@snx-v3/Constants": "workspace:*",
"@snx-v3/DepositModal": "workspace:*",
diff --git a/v3/ui/src/pages/Manage/Manage.tsx b/v3/ui/src/pages/Manage/Manage.tsx
index 56b47e4da..e8c9814f1 100644
--- a/v3/ui/src/pages/Manage/Manage.tsx
+++ b/v3/ui/src/pages/Manage/Manage.tsx
@@ -61,7 +61,7 @@ export const ManageUi: FC<{ collateralType: CollateralType }> = ({ collateralTyp
export const Manage = () => {
const params = useParams();
- const collateralType = useCollateralType(params.collateral);
+ const collateralType = useCollateralType(params.collateralSymbol);
if (!collateralType) {
return ; // TODO skeleton
diff --git a/v3/ui/src/pages/Manage/ManageActions.tsx b/v3/ui/src/pages/Manage/ManageActions.tsx
index b9e611de3..d27391eff 100644
--- a/v3/ui/src/pages/Manage/ManageActions.tsx
+++ b/v3/ui/src/pages/Manage/ManageActions.tsx
@@ -25,6 +25,7 @@ import { Withdraw } from './Withdraw';
import { Deposit } from './Deposit';
import { z } from 'zod';
import { RepayModal } from '@snx-v3/RepayModal';
+import { BorrowModal } from '@snx-v3/BorrowModal';
import { DepositModal } from '@snx-v3/DepositModal';
const validActions = ['borrow', 'deposit', 'repay', 'withdraw'] as const;
@@ -115,6 +116,7 @@ export const ManageAction = () => {
const [txnModalOpen, setTxnModalOpen] = useState(null);
const { debtChange, collateralChange, setCollateralChange, setDebtChange } =
useContext(ManagePositionContext);
+
const collateralType = useCollateralType(params.collateralSymbol);
const liquidityPosition = useLiquidityPosition({
accountId: params.accountId,
@@ -154,7 +156,7 @@ export const ManageAction = () => {
if (!form.reportValidity() || !isValid) {
return;
}
- if (parsedAction === 'repay' || parsedAction === 'deposit') {
+ if (parsedAction === 'repay' || parsedAction === 'deposit' || parsedAction === 'borrow') {
setTxnModalOpen(parsedAction);
} else {
// TODO add more hooks for all actions and remove this
@@ -205,6 +207,15 @@ export const ManageAction = () => {
}}
isOpen={txnModalOpen === 'repay'}
/>
+ {
+ liquidityPosition.refetch();
+ setCollateralChange(wei(0));
+ setDebtChange(wei(0));
+ setTxnModalOpen(null);
+ }}
+ isOpen={txnModalOpen === 'borrow'}
+ />
{
diff --git a/v3/ui/src/pages/Manage/ManageStats.tsx b/v3/ui/src/pages/Manage/ManageStats.tsx
index e3f93f5a7..da9db5190 100644
--- a/v3/ui/src/pages/Manage/ManageStats.tsx
+++ b/v3/ui/src/pages/Manage/ManageStats.tsx
@@ -104,7 +104,7 @@ export const ManageStats = () => {
const params = useParams();
const { debtChange, collateralChange } = useContext(ManagePositionContext);
- const collateralType = useCollateralType(params.collateral);
+ const collateralType = useCollateralType(params.collateralSymbol);
const { data: liquidityPosition } = useLiquidityPosition({
tokenAddress: collateralType?.tokenAddress,
accountId: params.accountId,
diff --git a/yarn.lock b/yarn.lock
index d8fb9595b..f534fde48 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7557,6 +7557,22 @@ __metadata:
languageName: unknown
linkType: soft
+"@snx-v3/BorrowModal@workspace:*, @snx-v3/BorrowModal@workspace:v3/components/BorrowModal":
+ version: 0.0.0-use.local
+ resolution: "@snx-v3/BorrowModal@workspace:v3/components/BorrowModal"
+ dependencies:
+ "@chakra-ui/react": ^2.2.8
+ "@snx-v3/Amount": "workspace:*"
+ "@snx-v3/ManagePositionContext": "workspace:*"
+ "@snx-v3/Multistep": "workspace:*"
+ "@snx-v3/txnReducer": "workspace:*"
+ "@snx-v3/useCollateralTypes": "workspace:*"
+ "@snx-v3/useParams": "workspace:*"
+ "@synthetixio/wei": "workspace:*"
+ react: ^18.2.0
+ languageName: unknown
+ linkType: soft
+
"@snx-v3/CollateralTypeSelector@workspace:*, @snx-v3/CollateralTypeSelector@workspace:v3/components/CollateralTypeSelector":
version: 0.0.0-use.local
resolution: "@snx-v3/CollateralTypeSelector@workspace:v3/components/CollateralTypeSelector"
@@ -7808,6 +7824,7 @@ __metadata:
"@snx-v3/Amount": "workspace:*"
"@snx-v3/Balance": "workspace:*"
"@snx-v3/BorderBox": "workspace:*"
+ "@snx-v3/BorrowModal": "workspace:*"
"@snx-v3/CollateralTypeSelector": "workspace:*"
"@snx-v3/Constants": "workspace:*"
"@snx-v3/DepositModal": "workspace:*"
@@ -7949,6 +7966,23 @@ __metadata:
languageName: unknown
linkType: soft
+"@snx-v3/useBorrow@workspace:v3/lib/useBorrow":
+ version: 0.0.0-use.local
+ resolution: "@snx-v3/useBorrow@workspace:v3/lib/useBorrow"
+ dependencies:
+ "@snx-v3/txnReducer": "workspace:*"
+ "@snx-v3/useBlockchain": "workspace:*"
+ "@snx-v3/useCoreProxy": "workspace:*"
+ "@snx-v3/useGasOptions": "workspace:*"
+ "@snx-v3/useGasPrice": "workspace:*"
+ "@snx-v3/useGasSpeed": "workspace:*"
+ "@synthetixio/wei": "workspace:*"
+ "@tanstack/react-query": ^4.3.4
+ ethers: ^5.7.2
+ react: ^18.2.0
+ languageName: unknown
+ linkType: soft
+
"@snx-v3/useCollateralTypes@workspace:*, @snx-v3/useCollateralTypes@workspace:v3/lib/useCollateralTypes":
version: 0.0.0-use.local
resolution: "@snx-v3/useCollateralTypes@workspace:v3/lib/useCollateralTypes"