diff --git a/e2e/screenshots/simulator/recurring/Recurring limit limit/form.jpg b/e2e/screenshots/simulator/recurring/Recurring limit limit/form.jpg
index f3c47ed17..6d9a18b96 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit limit/form.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit limit/form.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-input-price.jpg b/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-input-price.jpg
index ed4c4c146..c3b1bf725 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-input-price.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-input-price.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-results-animation.jpg b/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-results-animation.jpg
index 83c3a2370..5f4830fe0 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-results-animation.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit limit/simulator-results-animation.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring limit range/form.jpg b/e2e/screenshots/simulator/recurring/Recurring limit range/form.jpg
index 6537dcdd9..d9c738abc 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit range/form.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit range/form.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-input-price.jpg b/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-input-price.jpg
index b8c2c38ac..f6edc094b 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-input-price.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-input-price.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-results-summary.jpg b/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-results-summary.jpg
index 110c8134d..899ea664c 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-results-summary.jpg and b/e2e/screenshots/simulator/recurring/Recurring limit range/simulator-results-summary.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range limit/form.jpg b/e2e/screenshots/simulator/recurring/Recurring range limit/form.jpg
index 1577bf541..15b0dcc8d 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range limit/form.jpg and b/e2e/screenshots/simulator/recurring/Recurring range limit/form.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-input-price.jpg b/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-input-price.jpg
index 8d30f6e34..696fd8029 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-input-price.jpg and b/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-input-price.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-results-summary.jpg b/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-results-summary.jpg
index f6ac4183f..fd0f19f3f 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-results-summary.jpg and b/e2e/screenshots/simulator/recurring/Recurring range limit/simulator-results-summary.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range range/form.jpg b/e2e/screenshots/simulator/recurring/Recurring range range/form.jpg
index ab0a7aa36..83b90f319 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range range/form.jpg and b/e2e/screenshots/simulator/recurring/Recurring range range/form.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-input-price.jpg b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-input-price.jpg
index aecd2c71c..0ba9020ee 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-input-price.jpg and b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-input-price.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-animation.jpg b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-animation.jpg
index a1aa98801..56dbbe3d6 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-animation.jpg and b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-animation.jpg differ
diff --git a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-summary.jpg b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-summary.jpg
index 0174716b8..c5010ead2 100644
Binary files a/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-summary.jpg and b/e2e/screenshots/simulator/recurring/Recurring range range/simulator-results-summary.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy limit/create/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy limit/create/form.jpg
index 32d7b15a0..5a92b04dc 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy limit/create/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy limit/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy limit/deposit/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy limit/deposit/form.jpg
index 311468a0d..3808709ea 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy limit/deposit/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy limit/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy limit/editPrices/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy limit/editPrices/form.jpg
index b5cdaccfb..49a6742c9 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy limit/editPrices/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy limit/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy limit/withdraw/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy limit/withdraw/form.jpg
index f71ca2d63..21212475e 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy limit/withdraw/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy limit/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy range/create/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy range/create/form.jpg
index a4bff563f..c383b4852 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy range/create/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy range/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy range/deposit/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy range/deposit/form.jpg
index e8d989ca5..e528976c7 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy range/deposit/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy range/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy range/editPrices/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy range/editPrices/form.jpg
index 0720957c7..6d184c488 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy range/editPrices/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy range/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable buy range/withdraw/form.jpg b/e2e/screenshots/strategy/disposable/Disposable buy range/withdraw/form.jpg
index c1af62137..c880e0f62 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable buy range/withdraw/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable buy range/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell limit/create/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell limit/create/form.jpg
index d5713cb33..ad6bebd94 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell limit/create/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell limit/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell limit/deposit/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell limit/deposit/form.jpg
index edfaae038..496b30f30 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell limit/deposit/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell limit/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell limit/editPrices/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell limit/editPrices/form.jpg
index b82bab1e4..54cc1199d 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell limit/editPrices/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell limit/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell limit/withdraw/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell limit/withdraw/form.jpg
index 402eab9f8..4dc20a66c 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell limit/withdraw/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell limit/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell range/create/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell range/create/form.jpg
index 998b0c710..f0dc380b2 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell range/create/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell range/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell range/deposit/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell range/deposit/form.jpg
index 5c9df7ce2..fbc4a3e8a 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell range/deposit/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell range/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell range/editPrices/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell range/editPrices/form.jpg
index 58d75f45c..f5acedb0f 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell range/editPrices/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell range/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/disposable/Disposable sell range/withdraw/form.jpg b/e2e/screenshots/strategy/disposable/Disposable sell range/withdraw/form.jpg
index 105e68c63..c4899d026 100644
Binary files a/e2e/screenshots/strategy/disposable/Disposable sell range/withdraw/form.jpg and b/e2e/screenshots/strategy/disposable/Disposable sell range/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.jpg b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.jpg
index d2833696e..2342e3377 100644
Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.jpg and b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/create/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/create/form.jpg
index df46a36d0..805fa9736 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/create/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/create/my-strategy.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/create/my-strategy.jpg
index 22b7201b4..14ab32428 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/create/my-strategy.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/create/my-strategy.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/deposit/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/deposit/form.jpg
index bc3426363..b47df47fc 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/deposit/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/editPrices/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/editPrices/form.jpg
index ec96236b9..776ce132c 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/editPrices/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/renew/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/renew/form.jpg
index fb5308e53..8a0570c05 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/renew/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/renew/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit limit/withdraw/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit limit/withdraw/form.jpg
index 52d7aec09..a798e55a6 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit limit/withdraw/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit limit/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit range/create/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit range/create/form.jpg
index cc796f050..cbe9f54d0 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit range/create/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit range/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit range/deposit/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit range/deposit/form.jpg
index bbb871485..10edcf61e 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit range/deposit/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit range/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit range/editPrices/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit range/editPrices/form.jpg
index 8395e7298..d6563ac7f 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit range/editPrices/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit range/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit range/renew/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit range/renew/form.jpg
index 794ad17e7..f6aa66516 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit range/renew/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit range/renew/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring limit range/withdraw/form.jpg b/e2e/screenshots/strategy/recurring/Recurring limit range/withdraw/form.jpg
index 1952973d6..d27c8ea6f 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring limit range/withdraw/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring limit range/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range limit/create/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range limit/create/form.jpg
index 502dba98f..8826a3c42 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range limit/create/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range limit/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range limit/create/my-strategy.jpg b/e2e/screenshots/strategy/recurring/Recurring range limit/create/my-strategy.jpg
index 3671d6b2e..05ac6b35e 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range limit/create/my-strategy.jpg and b/e2e/screenshots/strategy/recurring/Recurring range limit/create/my-strategy.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range limit/editPrices/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range limit/editPrices/form.jpg
index c7ec3229d..5d52b545d 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range limit/editPrices/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range limit/editPrices/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range limit/renew/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range limit/renew/form.jpg
index 369fa69e5..b788fa5b9 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range limit/renew/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range limit/renew/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range limit/withdraw/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range limit/withdraw/form.jpg
index dad2b4f31..6b6e997b2 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range limit/withdraw/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range limit/withdraw/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range range/create/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range range/create/form.jpg
index 71a2a6705..b0d378f65 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range range/create/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range range/create/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range range/deposit/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range range/deposit/form.jpg
index cafdc3928..9bf794fab 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range range/deposit/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range range/deposit/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range range/renew/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range range/renew/form.jpg
index 93d3dc2f2..6c2fecbb7 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range range/renew/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range range/renew/form.jpg differ
diff --git a/e2e/screenshots/strategy/recurring/Recurring range range/withdraw/form.jpg b/e2e/screenshots/strategy/recurring/Recurring range range/withdraw/form.jpg
index ae3f54326..dc284e0d7 100644
Binary files a/e2e/screenshots/strategy/recurring/Recurring range range/withdraw/form.jpg and b/e2e/screenshots/strategy/recurring/Recurring range range/withdraw/form.jpg differ
diff --git a/src/assets/icons/action.svg b/src/assets/icons/action.svg
index 85a541bce..533f47604 100644
--- a/src/assets/icons/action.svg
+++ b/src/assets/icons/action.svg
@@ -1,5 +1,5 @@
-
= ({
{getFiatAsString(max)}
-
+ {marketPricePercentages && (
+
+ )}
diff --git a/src/components/strategies/create/overlapping/CreateOverlappingStrategy.tsx b/src/components/strategies/create/overlapping/CreateOverlappingStrategy.tsx
index 6db0f5c72..be11d586b 100644
--- a/src/components/strategies/create/overlapping/CreateOverlappingStrategy.tsx
+++ b/src/components/strategies/create/overlapping/CreateOverlappingStrategy.tsx
@@ -280,8 +280,8 @@ export const CreateOverlappingStrategy: FC = (
/>
= (props) => {
const budgetTooSmall = isOverlappingBudgetTooSmall(order0, order1);
const buyBudgetId = useId();
const sellBudgetId = useId();
+ const { user } = useWeb3();
- const checkInsufficientBalance = (balance: string, order: OrderCreate) => {
- if (new SafeDecimal(balance).lt(order.budget)) {
+ const checkInsufficientBalance = (
+ balance: string,
+ order: OrderCreate,
+ user?: string
+ ) => {
+ if (new SafeDecimal(balance).lt(order.budget) && !!user) {
order.setBudgetError('Insufficient balance');
} else {
order.setBudgetError('');
@@ -51,16 +57,16 @@ export const CreateOverlappingStrategyBudget: FC = (props) => {
// Check for error when buy budget changes
useEffect(() => {
const balance = token1BalanceQuery.data ?? '0';
- checkInsufficientBalance(balance, order0);
+ checkInsufficientBalance(balance, order0, user);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [order0.budget, token1BalanceQuery.data]);
+ }, [order0.budget, token1BalanceQuery.data, user]);
// Check for error when sell budget changes
useEffect(() => {
const balance = token0BalanceQuery.data ?? '0';
- checkInsufficientBalance(balance, order1);
+ checkInsufficientBalance(balance, order1, user);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [order1.budget, token0BalanceQuery.data]);
+ }, [order1.budget, token0BalanceQuery.data, user]);
const onBuyBudgetChange = (value: string) => {
order0.setBudget(value);
@@ -76,26 +82,33 @@ export const CreateOverlappingStrategyBudget: FC = (props) => {
if (!quote || !base) return <>>;
return (
<>
-
- {minAboveMarket && }
{maxBelowMarket && }
+
+ {minAboveMarket && }
+
{budgetTooSmall && (
) => {
e.preventDefault();
if (type === 'withdraw') {
- depositOrWithdrawFunds();
+ const withdrawAll =
+ (order0.budget || '0') === strategy.order0.balance &&
+ (order1.budget || '0') === strategy.order1.balance;
+ if (withdrawAll) {
+ openWithdrawModal();
+ } else {
+ depositOrWithdrawFunds();
+ }
} else {
if (approval.approvalRequired) {
openModal('txConfirm', {
@@ -138,6 +151,21 @@ export const EditStrategyBudgetContent = ({
return MarginalPriceOptions.reset;
};
+ const { deleteStrategy } = useDeleteStrategy();
+
+ const openWithdrawModal = () => {
+ openModal('withdrawOrDelete', {
+ onWithdraw: depositOrWithdrawFunds,
+ onDelete: () =>
+ deleteStrategy(strategy, setIsProcessing, () =>
+ carbonEvents.strategyEdit.strategyDelete({
+ strategyId: strategy.id,
+ ...strategyEventData,
+ })
+ ),
+ });
+ };
+
const depositOrWithdrawFunds = () => {
const buyOption = getMarginalOption(order0);
const sellOption = getMarginalOption(order1);
diff --git a/src/components/strategies/edit/EditStrategyHeader.tsx b/src/components/strategies/edit/EditStrategyHeader.tsx
index 44d0320cf..ad1da678e 100644
--- a/src/components/strategies/edit/EditStrategyHeader.tsx
+++ b/src/components/strategies/edit/EditStrategyHeader.tsx
@@ -19,8 +19,8 @@ export const EditStrategyHeader = ({
const titleByType: { [key in EditTypes]: string } = {
renew: 'Renew Strategy',
editPrices: 'Edit Prices',
- deposit: 'Deposit Budget',
- withdraw: 'Withdraw Budget',
+ deposit: 'Deposit Budgets',
+ withdraw: 'Withdraw Budgets',
};
return (
diff --git a/src/components/strategies/edit/overlapping/DepositOverlappingStrategy.tsx b/src/components/strategies/edit/overlapping/DepositOverlappingStrategy.tsx
index 65e1d4d40..b58618d0b 100644
--- a/src/components/strategies/edit/overlapping/DepositOverlappingStrategy.tsx
+++ b/src/components/strategies/edit/overlapping/DepositOverlappingStrategy.tsx
@@ -160,16 +160,35 @@ export const DepositOverlappingStrategy: FC = (props) => {
- Deposit Budget
+ Deposit Budgets
+
+
+
+
@@ -180,19 +199,6 @@ export const DepositOverlappingStrategy: FC = (props) => {
/>
-
-
-
-
{budgetTooSmall && (
= (props) => {
Edit Budget
-
-
-
-
@@ -194,6 +179,28 @@ export const EditOverlappingStrategyBudget: FC = (props) => {
{maxBelowMarket && }
+
+
+
+
+
{!minAboveMarket && !maxBelowMarket && (
The required 2nd budget will be calculated to maintain overlapping
@@ -226,7 +233,8 @@ const Explanation: FC<{ base?: Token; buy?: boolean }> = ({ base, buy }) => {
The market price is outside the ranges you set for
{buy ? 'buying' : 'selling'}
- {base?.symbol}. Budget for buying {base?.symbol} is not required.
+ {base?.symbol}. Budget for {buy ? 'buying' : 'selling'} {base?.symbol} is
+ not required.
= (props) => {
/>
= (props) => {
const { strategy, order0, order1 } = props;
const { base, quote } = strategy;
- const buyId = useId();
- const sellId = useId();
const tokenBaseBalanceQuery = useGetTokenBalance(base);
const tokenQuoteBalanceQuery = useGetTokenBalance(quote);
@@ -163,17 +161,40 @@ export const WithdrawOverlappingStrategy: FC = (props) => {
- Withdraw Budget
+ Withdraw Budgets
+
+
+
+
= (props) => {
buy
/>
-
-
-
{budgetTooSmall && (
= (props) => {
htmlFor={`${buyBudgetId} ${sellBudgetId}`}
/>
)}
-
+ {!withdrawAll && (
+
+ )}
>
);
diff --git a/src/components/strategies/overlapping/OverlappingStrategySpread.tsx b/src/components/strategies/overlapping/OverlappingStrategySpread.tsx
index 731b6429e..ee4d26a92 100644
--- a/src/components/strategies/overlapping/OverlappingStrategySpread.tsx
+++ b/src/components/strategies/overlapping/OverlappingStrategySpread.tsx
@@ -2,15 +2,12 @@ import {
useRef,
FC,
KeyboardEvent,
- Dispatch,
- SetStateAction,
useState,
FocusEvent,
ChangeEvent,
} from 'react';
import { ReactComponent as IconWarning } from 'assets/icons/warning.svg';
import { cn, formatNumber, sanitizeNumber } from 'utils/helpers';
-import { OrderCreate } from '../create/useOrder';
import { getMaxSpread } from 'components/strategies/overlapping/utils';
import styles from './OverlappingStrategySpread.module.css';
@@ -19,9 +16,9 @@ interface Props {
defaultValue: number;
options: number[];
spread: number;
- order0: OrderCreate;
- order1: OrderCreate;
- setSpread: Dispatch>;
+ buyMin: number;
+ sellMax: number;
+ setSpread: (value: number) => void;
}
const getWarning = (maxSpread: number) => {
@@ -31,13 +28,10 @@ const getWarning = (maxSpread: number) => {
const round = (value: number) => Math.round(value * 100) / 100;
export const OverlappingStrategySpread: FC = (props) => {
- const { defaultValue, options, spread, setSpread } = props;
+ const { defaultValue, options, spread, setSpread, buyMin, sellMax } = props;
const root = useRef(null);
const inOptions = options.includes(spread);
const hasError = spread <= 0 || spread > 100;
- const { order0, order1 } = props;
- const buyMin = Number(order0.min);
- const sellMax = Number(order1.max);
const [warning, setWarning] = useState('');
const selectSpread = (value: number) => {
diff --git a/src/components/strategies/overview/strategyBlock/StrategyBlockManage.tsx b/src/components/strategies/overview/strategyBlock/StrategyBlockManage.tsx
index de692f0d6..f3a81cd2f 100644
--- a/src/components/strategies/overview/strategyBlock/StrategyBlockManage.tsx
+++ b/src/components/strategies/overview/strategyBlock/StrategyBlockManage.tsx
@@ -85,6 +85,48 @@ export const StrategyBlockManage: FC = (props) => {
},
});
}
+ const isDisposable =
+ +strategy.order0.startRate === 0 ||
+ +strategy.order0.endRate === 0 ||
+ +strategy.order1.startRate === 0 ||
+ +strategy.order1.endRate === 0;
+
+ if (!isDisposable) {
+ items.push({
+ id: 'simulate',
+ name: 'Simulate Strategy',
+ action: () => {
+ if (isOverlapping) {
+ navigate({
+ to: '/simulate/overlapping',
+ search: {
+ baseToken: strategy.base.address,
+ quoteToken: strategy.quote.address,
+ buyMin: strategy.order0.startRate,
+ sellMax: strategy.order1.endRate,
+ },
+ });
+ } else {
+ navigate({
+ to: '/simulate/recurring',
+ search: {
+ baseToken: strategy.base.address,
+ quoteToken: strategy.quote.address,
+ buyMin: strategy.order0.startRate,
+ buyMax: strategy.order0.endRate,
+ buyBudget: strategy.order0.balance,
+ buyIsRange: strategy.order0.endRate !== strategy.order0.startRate,
+ sellMin: strategy.order1.startRate,
+ sellMax: strategy.order1.endRate,
+ sellBudget: strategy.order1.balance,
+ sellIsRange:
+ strategy.order1.endRate !== strategy.order1.startRate,
+ },
+ });
+ }
+ },
+ });
+ }
if (isExplorer) {
items.push({
diff --git a/src/components/strategies/overview/strategyBlock/utils.ts b/src/components/strategies/overview/strategyBlock/utils.ts
index 661f4209e..1c7db7a14 100644
--- a/src/components/strategies/overview/strategyBlock/utils.ts
+++ b/src/components/strategies/overview/strategyBlock/utils.ts
@@ -44,6 +44,7 @@ const tooltipTextByStrategyEditOptionsId = {
renewStrategy: 'Renew an inactive strategy',
manageNotifications: 'Get notified when someone trades against your strategy',
walletOwner: 'View all strategies from the owner of this strategy',
+ simulate: 'Run a simulation on this strategy',
};
export const getTooltipTextByStrategyEditOptionsId = (
diff --git a/src/hooks/useSimulatorOverlappingInput.ts b/src/hooks/useSimulatorOverlappingInput.ts
new file mode 100644
index 000000000..b65308c1f
--- /dev/null
+++ b/src/hooks/useSimulatorOverlappingInput.ts
@@ -0,0 +1,160 @@
+import { useNavigate } from '@tanstack/react-router';
+import { useDebouncedValue } from 'hooks/useDebouncedValue';
+import { StrategyInputOrder } from 'hooks/useStrategyInput';
+import { ChartPrices } from 'components/simulator/input/d3Chart/D3ChartCandlesticks';
+import { useTokens } from 'hooks/useTokens';
+import { SimulatorInputOverlappingSearch } from 'libs/routing/routes/sim';
+import { Token } from 'libs/tokens';
+import { useCallback, useMemo, useState } from 'react';
+
+export interface InternalSimulatorOverlappingInput
+ extends SimulatorInputOverlappingSearch {
+ buyMax?: string;
+ sellMin?: string;
+ buyBudget?: string;
+ sellBudget?: string;
+ sellBudgetError?: string;
+ buyBudgetError?: string;
+ buyPriceError?: string;
+ sellPriceError?: string;
+}
+
+export type SimulatorOverlappingInputDispatch = <
+ T extends InternalSimulatorOverlappingInput,
+ K extends keyof T
+>(
+ key: K,
+ value: T[K],
+ setBounds?: boolean
+) => void;
+
+interface Props {
+ searchState: SimulatorInputOverlappingSearch;
+}
+
+export interface SimulatorInputOverlappingValues {
+ baseToken?: Token;
+ quoteToken?: Token;
+ buy: Omit;
+ sell: Omit;
+ start?: string;
+ end?: string;
+ spread: string;
+}
+
+export const useSimulatorOverlappingInput = ({ searchState }: Props) => {
+ const { getTokenById } = useTokens();
+ const navigate = useNavigate();
+ const [_state, setState] =
+ useState(searchState);
+
+ const baseToken = useMemo(
+ () => getTokenById(_state.baseToken),
+ [_state.baseToken, getTokenById]
+ );
+ const quoteToken = useMemo(
+ () => getTokenById(_state.quoteToken),
+ [_state.quoteToken, getTokenById]
+ );
+
+ const state = buildStrategyInputState(_state, baseToken, quoteToken);
+
+ const setSearch = useCallback(
+ (search: InternalSimulatorOverlappingInput) => {
+ const {
+ buyMax,
+ sellMin,
+ buyBudget,
+ sellBudget,
+ buyBudgetError,
+ sellBudgetError,
+ buyPriceError,
+ sellPriceError,
+ ...newSearch
+ } = search;
+
+ void navigate({
+ search: newSearch,
+ params: {},
+ replace: true,
+ resetScroll: false,
+ });
+ },
+ [navigate]
+ );
+
+ useDebouncedValue(_state, 300, { cb: setSearch });
+
+ const [bounds, setBounds] = useState({
+ buy: { min: searchState.buyMin || '', max: '' },
+ sell: { min: '', max: searchState.sellMax || '' },
+ });
+
+ const dispatch: SimulatorOverlappingInputDispatch = useCallback(
+ (key, value, updateBounds = true) => {
+ setState((state) => ({ ...state, [key]: value }));
+
+ if (!updateBounds) {
+ return;
+ }
+
+ switch (key) {
+ case 'buyMin':
+ setBounds((bounds) => ({
+ ...bounds,
+ buy: { ...bounds.buy, min: value as string },
+ }));
+ break;
+ case 'buyMax':
+ setBounds((bounds) => ({
+ ...bounds,
+ buy: { ...bounds.buy, max: value as string },
+ }));
+ break;
+ case 'sellMin':
+ setBounds((bounds) => ({
+ ...bounds,
+ sell: { ...bounds.sell, min: value as string },
+ }));
+ break;
+ case 'sellMax':
+ setBounds((bounds) => ({
+ ...bounds,
+ sell: { ...bounds.sell, max: value as string },
+ }));
+ break;
+ }
+ },
+ []
+ );
+
+ return { dispatch, state, bounds, searchState };
+};
+
+export const buildStrategyInputState = (
+ state: InternalSimulatorOverlappingInput,
+ baseToken?: Token,
+ quoteToken?: Token
+): SimulatorInputOverlappingValues => {
+ return {
+ baseToken,
+ quoteToken,
+ buy: {
+ min: state.buyMin || '',
+ max: state.buyMax || '',
+ budget: state.buyBudget || '',
+ budgetError: state.buyBudgetError,
+ priceError: state.buyPriceError,
+ },
+ sell: {
+ min: state.sellMin || '',
+ max: state.sellMax || '',
+ budget: state.sellBudget || '',
+ budgetError: state.sellBudgetError,
+ priceError: state.sellPriceError,
+ },
+ start: state.start || undefined,
+ end: state.end || undefined,
+ spread: state.spread || '',
+ };
+};
diff --git a/src/hooks/useStrategyInput.ts b/src/hooks/useStrategyInput.ts
index ed64cf472..1772b3b18 100644
--- a/src/hooks/useStrategyInput.ts
+++ b/src/hooks/useStrategyInput.ts
@@ -4,7 +4,6 @@ import { useTokens } from 'hooks/useTokens';
import { StrategyInputSearch } from 'libs/routing/routes/sim';
import { Token } from 'libs/tokens';
import { useCallback, useMemo, useState } from 'react';
-import { stringToBoolean } from 'utils/helpers';
export interface InternalStrategyInput extends StrategyInputSearch {
sellBudgetError?: string;
@@ -37,6 +36,7 @@ export interface StrategyInputValues {
sell: StrategyInputOrder;
start?: string;
end?: string;
+ overlappingSpread?: string;
}
interface Props {
@@ -46,11 +46,7 @@ interface Props {
export const useStrategyInput = ({ searchState }: Props) => {
const { getTokenById } = useTokens();
const navigate = useNavigate();
- const [_state, setState] = useState({
- ...searchState,
- sellIsRange: stringToBoolean(String(searchState.sellIsRange), true),
- buyIsRange: stringToBoolean(String(searchState.buyIsRange), true),
- });
+ const [_state, setState] = useState(searchState);
const baseToken = useMemo(
() => getTokenById(_state.baseToken),
diff --git a/src/libs/modals/modals/ModalWithdrawOrDelete.tsx b/src/libs/modals/modals/ModalWithdrawOrDelete.tsx
new file mode 100644
index 000000000..a79363b3c
--- /dev/null
+++ b/src/libs/modals/modals/ModalWithdrawOrDelete.tsx
@@ -0,0 +1,51 @@
+import { useModal } from 'hooks/useModal';
+import { ModalFC } from 'libs/modals/modals.types';
+import { Button } from 'components/common/button';
+import { IconTitleText } from 'components/common/iconTitleText/IconTitleText';
+import { ModalOrMobileSheet } from 'libs/modals/ModalOrMobileSheet';
+import { ReactComponent as IconDelete } from 'assets/icons/delete.svg';
+
+export type ModalWithdrawOrDeleteData = {
+ onDelete: () => void;
+ onWithdraw: () => void;
+};
+
+export const ModalWithdrawOrDelete: ModalFC = ({
+ id,
+ data: { onDelete, onWithdraw },
+}) => {
+ const { closeModal } = useModal();
+
+ return (
+
+
+ }
+ title="This strategy will become inactive once the budget is removed"
+ text="Delete this strategy to keep things tidy"
+ />
+
+
+
+
+ );
+};
diff --git a/src/libs/modals/modals/index.ts b/src/libs/modals/modals/index.ts
index 1a6e921f0..23969c2b3 100644
--- a/src/libs/modals/modals/index.ts
+++ b/src/libs/modals/modals/index.ts
@@ -1,3 +1,7 @@
+import {
+ ModalWithdrawOrDelete,
+ ModalWithdrawOrDeleteData,
+} from 'libs/modals/modals/ModalWithdrawOrDelete';
import { ModalWallet } from 'libs/modals/modals/WalletModal/ModalWallet';
import {
ModalTokenList,
@@ -71,6 +75,7 @@ export interface ModalSchema {
confirmWithdrawStrategy: ModalConfirmWithdrawData;
confirmDeleteStrategy: ModalConfirmDeleteData;
simulatorDisclaimer: ModalSimulatorDisclaimerData;
+ withdrawOrDelete: ModalWithdrawOrDeleteData;
}
// Step 2: Create component in modals/modals folder
@@ -93,4 +98,5 @@ export const MODAL_COMPONENTS: TModals = {
confirmWithdrawStrategy: (props) => ModalConfirmWithdraw(props),
confirmDeleteStrategy: (props) => ModalConfirmDelete(props),
simulatorDisclaimer: (props) => ModalSimulatorDisclaimer(props),
+ withdrawOrDelete: (props) => ModalWithdrawOrDelete(props),
};
diff --git a/src/libs/queries/extApi/simulator.ts b/src/libs/queries/extApi/simulator.ts
index c7b84deb7..5a1c2f930 100644
--- a/src/libs/queries/extApi/simulator.ts
+++ b/src/libs/queries/extApi/simulator.ts
@@ -56,45 +56,70 @@ export type SimulatorReturn = {
gains?: number;
};
+export interface SimulatorDataNew {
+ date: number;
+ price: string;
+ sell: string;
+ buy: string;
+ portfolioOverHodlInPercent: string;
+ portfolioValueInQuote: string;
+ hodlValueInQuote: string;
+ baseBalance: string;
+ basePortion: string;
+ quoteBalance: string;
+ quotePortion: string;
+}
+
+interface SimulatorBoundsNew {
+ sellMin: string;
+ sellMax: string;
+ buyMin: string;
+ buyMax: string;
+}
+
+export interface SimulatorReturnNew {
+ data: Array;
+ bounds: SimulatorBoundsNew;
+ roiInPercent: string;
+ gainsInQuote: string;
+}
+
export type SimulatorAPIParams = Omit<
SimulatorResultSearch,
- 'buyIsRange' | 'sellIsRange'
+ 'buyIsRange' | 'sellIsRange' | 'type' | 'overlappingSpread'
>;
-export const useGetSimulator = (params: SimulatorAPIParams) => {
+export const useGetSimulator = (search: SimulatorResultSearch) => {
return useQuery(
- QueryKey.simulator(params),
+ QueryKey.simulator(search),
async () => {
try {
+ const { buyIsRange, sellIsRange, type, spread, ...params } = search;
const res = await carbonApi.getSimulator(params);
+ // TODO cleanup schema
const data: SimulatorReturn = {
- data: res.dates.map((d, i) => ({
- date: d,
- price: Number(res.prices[i]),
- ask: Number(res.ask[i]),
- bid: Number(res.bid[i]),
- balanceRISK: Number(res.RISK.balance[i]),
- portionRISK: Number(res.portfolio_risk[i]),
- balanceCASH: Number(res.CASH.balance[i]),
- portionCASH: Number(res.portfolio_cash[i]),
- portfolioValue: Number(res.portfolio_value[i]),
- hodlValue: Number(res.hodl_value[i]),
- portfolioOverHodl: Number(res.portfolio_over_hodl[i]),
+ data: res.data.map((x) => ({
+ date: x.date,
+ price: Number(x.price),
+ ask: Number(x.sell),
+ bid: Number(x.buy),
+ balanceRISK: Number(x.baseBalance),
+ portionRISK: Number(x.basePortion),
+ balanceCASH: Number(x.quoteBalance),
+ portionCASH: Number(x.quotePortion),
+ portfolioValue: Number(x.portfolioValueInQuote),
+ hodlValue: Number(x.hodlValueInQuote),
+ portfolioOverHodl: Number(x.portfolioOverHodlInPercent),
})),
- roi: Number(
- res.portfolio_over_hodl[res.portfolio_over_hodl.length - 1]
- ),
- gains: Number(
- Number(res.portfolio_value[res.portfolio_value.length - 1]) -
- Number(res.hodl_value[res.hodl_value.length - 1])
- ),
bounds: {
- askMax: Number(res.max_ask),
- askMin: Number(res.min_ask),
- bidMax: Number(res.max_bid),
- bidMin: Number(res.min_bid),
+ askMax: Number(res.bounds.sellMax),
+ askMin: Number(res.bounds.sellMin),
+ bidMax: Number(res.bounds.buyMax),
+ bidMin: Number(res.bounds.buyMin),
},
+ roi: Number(res.roiInPercent),
+ gains: Number(res.gainsInQuote),
};
return data;
diff --git a/src/libs/queries/queryKey.ts b/src/libs/queries/queryKey.ts
index 11d93131c..022cdc401 100644
--- a/src/libs/queries/queryKey.ts
+++ b/src/libs/queries/queryKey.ts
@@ -1,6 +1,6 @@
import { MatchActionBNStr, TokenPair } from '@bancor/carbon-sdk';
-import { SimulatorAPIParams } from 'libs/queries/extApi/simulator';
import { TokenPriceHistorySearch } from 'libs/queries/extApi/tokenPrice';
+import { SimulatorResultSearch } from 'libs/routing';
import { buildTokenPairKey } from 'utils/helpers';
import { QueryActivityParams } from './extApi/activity';
@@ -16,7 +16,7 @@ export namespace QueryKey {
];
export const roi = () => [...extAPI, 'roi'];
- export const simulator = (params: SimulatorAPIParams) => [
+ export const simulator = (params: SimulatorResultSearch) => [
...extAPI,
'simulator',
params,
diff --git a/src/libs/routing/externalLinks.ts b/src/libs/routing/externalLinks.ts
index fabe20005..06fcc60ee 100644
--- a/src/libs/routing/externalLinks.ts
+++ b/src/libs/routing/externalLinks.ts
@@ -19,4 +19,6 @@ export const externalLinks = {
'https://home.treasury.gov/policy-issues/financial-sanctions/recent-actions/20220808',
terms: config.appUrl + '/terms',
privacy: config.appUrl + '/privacy',
+ whatIsOverlapping:
+ 'https://faq.carbondefi.xyz/what-is-an-overlapping-strategy#overlapping-budget-dynamics',
};
diff --git a/src/libs/routing/router.tsx b/src/libs/routing/router.tsx
index 19f9f11c6..cbdfb7790 100644
--- a/src/libs/routing/router.tsx
+++ b/src/libs/routing/router.tsx
@@ -1,18 +1,10 @@
import { Router } from '@tanstack/react-router';
import { routeTree } from 'libs/routing/routes';
+import { parseSearchWith } from 'libs/routing/utils';
export const router = new Router({
routeTree,
- parseSearch: (searchStr) => {
- const searchParams = new URLSearchParams(searchStr);
- return Object.fromEntries(searchParams.entries());
- },
- stringifySearch: (search) => {
- const searchParams = new URLSearchParams();
- for (const key in search) searchParams.set(key, search[key]);
- const searchStr = searchParams.toString();
- return searchStr ? `?${searchStr}` : '';
- },
+ parseSearch: parseSearchWith(JSON.parse),
});
declare module '@tanstack/react-router' {
diff --git a/src/libs/routing/routes/index.ts b/src/libs/routing/routes/index.ts
index 0520960b6..f910614ef 100644
--- a/src/libs/routing/routes/index.ts
+++ b/src/libs/routing/routes/index.ts
@@ -23,8 +23,9 @@ import {
} from 'libs/routing/routes/myStrategies';
import { rootRoute } from 'libs/routing/routes/root';
import {
- simulatorInputRoute,
- simulatorRedirect,
+ simulatorInputOverlappingRoute,
+ simulatorInputRecurringRoute,
+ simulatorInputRootRoute,
simulatorResultRoute,
simulatorRootRoute,
} from 'libs/routing/routes/sim';
@@ -67,8 +68,10 @@ export const routeTree = rootRoute.addChildren([
strategyActivityPage,
]),
simulatorRootRoute.addChildren([
- simulatorRedirect,
- simulatorInputRoute,
+ simulatorInputRootRoute.addChildren([
+ simulatorInputRecurringRoute,
+ simulatorInputOverlappingRoute,
+ ]),
simulatorResultRoute,
]),
]);
diff --git a/src/libs/routing/routes/sim.tsx b/src/libs/routing/routes/sim.tsx
index c91ca818a..1faf93cb0 100644
--- a/src/libs/routing/routes/sim.tsx
+++ b/src/libs/routing/routes/sim.tsx
@@ -1,11 +1,14 @@
import { redirect, Route } from '@tanstack/react-router';
import { SimulatorProvider } from 'components/simulator/result/SimulatorProvider';
+import { endOfDay, getUnixTime, startOfDay, sub } from 'date-fns';
import { rootRoute } from 'libs/routing/routes/root';
import { validAddress, validBoolean, validNumber } from 'libs/routing/utils';
-import { defaultEnd, defaultStart, SimulatorPage } from 'pages/simulator';
+import { SimulatorPage } from 'pages/simulator';
+import { SimulatorInputOverlappingPage } from 'pages/simulator/overlapping';
+import { SimulatorInputRecurringPage } from 'pages/simulator/recurring';
import { SimulatorResultPage } from 'pages/simulator/result';
import { config } from 'services/web3/config';
-import { roundSearchParam, stringToBoolean } from 'utils/helpers';
+import { roundSearchParam } from 'utils/helpers';
import * as v from 'valibot';
export const simulatorRootRoute = new Route({
@@ -13,48 +16,34 @@ export const simulatorRootRoute = new Route({
path: '/simulate',
});
-export interface StrategyInputSearch {
+export interface StrategyInputBase {
baseToken?: string;
quoteToken?: string;
- sellBudget?: string;
- sellMax?: string;
- sellMin?: string;
- sellIsRange?: boolean;
- buyMax?: string;
- buyMin?: string;
- buyBudget?: string;
- buyIsRange?: boolean;
start?: string;
end?: string;
}
-export const simulatorRedirect = new Route({
+export const simulatorInputRootRoute = new Route({
getParentRoute: () => simulatorRootRoute,
path: '/',
- beforeLoad: () => {
- redirect({
- to: '/simulate/$simulationType',
- params: { simulationType: 'recurring' },
- throw: true,
- replace: true,
- });
- },
- component: SimulatorPage,
-});
-
-export type SimulatorType = 'recurring' | 'overlapping';
-
-export const simulatorInputRoute = new Route({
- getParentRoute: () => simulatorRootRoute,
- path: '$simulationType',
component: SimulatorPage,
- parseParams: (params: Record) => {
- return { simulationType: params.simulationType as SimulatorType };
+ beforeLoad: ({ location }) => {
+ if (location.pathname === '/simulate' || location.pathname === '/simulate/')
+ redirect({
+ to: '/simulate/recurring',
+ throw: true,
+ replace: true,
+ });
},
- validateSearch: (search: Record): StrategyInputSearch => {
+ validateSearch: (search: Record): StrategyInputBase => {
const start =
- Number(search.start) > 0 ? search.start : defaultStart().toString();
- const end = Number(search.end) > 0 ? search.end : defaultEnd().toString();
+ Number(search.start) > 0
+ ? (search.start as string)
+ : getUnixTime(startOfDay(sub(new Date(), { days: 364 }))).toString();
+ const end =
+ Number(search.end) > 0
+ ? (search.end as string)
+ : getUnixTime(endOfDay(new Date())).toString();
if (Number(start) >= Number(end)) {
throw new Error('Invalid date range');
@@ -66,6 +55,34 @@ export const simulatorInputRoute = new Route({
const quoteToken = v.is(validAddress, search.quoteToken)
? search.quoteToken
: config.tokens.USDC;
+
+ return {
+ baseToken,
+ quoteToken,
+ start,
+ end,
+ };
+ },
+});
+
+export interface StrategyInputSearch extends StrategyInputBase {
+ sellBudget?: string;
+ sellMax?: string;
+ sellMin?: string;
+ sellIsRange?: boolean;
+ buyMax?: string;
+ buyMin?: string;
+ buyBudget?: string;
+ buyIsRange?: boolean;
+}
+
+export type SimulatorType = 'recurring' | 'overlapping';
+
+export const simulatorInputRecurringRoute = new Route({
+ getParentRoute: () => simulatorInputRootRoute,
+ path: 'recurring',
+ component: SimulatorInputRecurringPage,
+ validateSearch: (search: Record): StrategyInputSearch => {
const sellMax = v.is(validNumber, search.sellMax)
? roundSearchParam(search.sellMax)
: '';
@@ -84,12 +101,12 @@ export const simulatorInputRoute = new Route({
const buyBudget = v.is(validNumber, search.buyBudget)
? roundSearchParam(search.buyBudget)
: '';
- const sellIsRange = stringToBoolean(search.sellIsRange, true);
- const buyIsRange = stringToBoolean(search.buyIsRange, true);
+ const sellIsRange =
+ typeof search.sellIsRange === 'boolean' ? search.sellIsRange : true;
+ const buyIsRange =
+ typeof search.buyIsRange === 'boolean' ? search.buyIsRange : true;
return {
- baseToken,
- quoteToken,
sellMax: sellIsRange ? sellMax : sellMin,
sellMin,
sellBudget,
@@ -98,13 +115,45 @@ export const simulatorInputRoute = new Route({
buyBudget,
sellIsRange,
buyIsRange,
- start,
- end,
};
},
});
-export type SimulatorResultSearch = Required;
+export interface SimulatorInputOverlappingSearch extends StrategyInputBase {
+ sellMax?: string;
+ buyMin?: string;
+ spread?: string;
+}
+
+export const simulatorInputOverlappingRoute = new Route({
+ getParentRoute: () => simulatorInputRootRoute,
+ path: 'overlapping',
+ component: SimulatorInputOverlappingPage,
+ validateSearch: (
+ search: Record
+ ): SimulatorInputOverlappingSearch => {
+ const sellMax = v.is(validNumber, search.sellMax)
+ ? roundSearchParam(search.sellMax)
+ : '';
+ const buyMin = v.is(validNumber, search.buyMin)
+ ? roundSearchParam(search.buyMin)
+ : '';
+ const spread = v.is(validNumber, search.spread) ? search.spread : '1';
+
+ return {
+ sellMax,
+ buyMin,
+ spread,
+ };
+ },
+});
+
+export type SimulatorResultSearch = Required & {
+ type: SimulatorType;
+ buyMarginal?: string;
+ sellMarginal?: string;
+ spread?: string;
+};
export const simulatorResultRoute = new Route({
getParentRoute: () => simulatorRootRoute,
@@ -163,11 +212,17 @@ export const simulatorResultRoute = new Route({
sellMax: search.sellMax,
sellMin: search.sellMin,
sellBudget: search.sellBudget || '0',
- sellIsRange: stringToBoolean(search.sellIsRange),
+ // TODO add validation
+ sellMarginal: search.sellMarginal || '0',
+ sellIsRange: search.sellIsRange,
buyMax: search.buyMax,
buyMin: search.buyMin,
+ buyMarginal: search.buyMarginal || '0',
buyBudget: search.buyBudget || '0',
- buyIsRange: stringToBoolean(search.buyIsRange),
+ buyIsRange: search.buyIsRange,
+ // TODO add validation
+ spread: search.spread,
+ type: search.type as SimulatorType,
};
},
// @ts-ignore
diff --git a/src/libs/routing/utils.ts b/src/libs/routing/utils.ts
index 849dd7317..42210903c 100644
--- a/src/libs/routing/utils.ts
+++ b/src/libs/routing/utils.ts
@@ -94,8 +94,8 @@ export const validAddress = v.string([
}
}),
]);
-export const validBoolean = v.string([
- v.custom((value) => value === 'true' || value === 'false'),
+export const validBoolean = v.boolean([
+ v.custom((value) => value === true || value === false),
]);
export const validateSearchParams = (
diff --git a/src/pages/simulator/index.tsx b/src/pages/simulator/index.tsx
index 5a2eb0de9..947d2a7fb 100644
--- a/src/pages/simulator/index.tsx
+++ b/src/pages/simulator/index.tsx
@@ -1,16 +1,11 @@
-import { useNavigate, useParams, useSearch } from '@tanstack/react-router';
-import { SimInputChart } from 'components/simulator/input/SimInputChart';
-import { SimInputOverlapping } from 'components/simulator/input/SimInputOverlapping';
-import { SimInputRecurring } from 'components/simulator/input/SimInputRecurring';
+import { Outlet } from '@tanstack/react-router';
import { SimInputStrategyType } from 'components/simulator/input/SimInputStrategyType';
import { SimInputTokenSelection } from 'components/simulator/input/SimInputTokenSelection';
import { useSimDisclaimer } from 'components/simulator/input/useSimDisclaimer';
import { useBreakpoints } from 'hooks/useBreakpoints';
-import { useSimulatorInput } from 'hooks/useSimulatorInput';
-import { FormEvent, useState } from 'react';
+import { simulatorInputRootRoute } from 'libs/routing/routes/sim';
import { SimulatorMobilePlaceholder } from 'components/simulator/mobile-placeholder';
import { useGetTokenPriceHistory } from 'libs/queries/extApi/tokenPrice';
-import { Button } from 'components/common/button';
import { getUnixTime, subDays } from 'date-fns';
export const defaultStart = () => getUnixTime(subDays(new Date(), 364));
@@ -18,108 +13,30 @@ export const defaultEnd = () => getUnixTime(new Date());
export const SimulatorPage = () => {
useSimDisclaimer();
+ const searchState = simulatorInputRootRoute.useSearch();
+ const { isError } = useGetTokenPriceHistory(searchState);
const { aboveBreakpoint } = useBreakpoints();
- const navigate = useNavigate();
- const { simulationType } = useParams({ from: '/simulate/$simulationType' });
- const searchState = useSearch({
- from: '/simulate/$simulationType',
- });
- const { dispatch, state, bounds } = useSimulatorInput({ searchState });
- const { data, isLoading, isError } = useGetTokenPriceHistory({
- baseToken: state.baseToken?.address,
- quoteToken: state.quoteToken?.address,
- start: state.start,
- end: state.end,
- });
-
- const [initBuyRange, setInitBuyRange] = useState(true);
- const [initSellRange, setInitSellRange] = useState(true);
-
- const noBudget = Number(state.buy.budget) + Number(state.sell.budget) <= 0;
- const noBudgetText =
- !isError && noBudget && 'Please add Sell and/or Buy budgets';
- const loadingText = isLoading && 'Loading price history...';
- const priceError = state.buy.priceError || state.sell.priceError;
-
- const btnDisabled = isLoading || isError || noBudget || !!priceError;
if (!aboveBreakpoint('md')) return ;
- const submit = (e: FormEvent) => {
- e.preventDefault();
- if (isLoading || isError || noBudget) return;
- const start = state.start ?? defaultStart();
- const end = state.end ?? defaultEnd();
- navigate({
- to: '/simulate/result',
- search: {
- baseToken: state.baseToken?.address || '',
- quoteToken: state.quoteToken?.address || '',
- buyMin: state.buy.min,
- buyMax: state.buy.max,
- buyBudget: state.buy.budget,
- buyIsRange: state.buy.isRange,
- sellMin: state.sell.min,
- sellMax: state.sell.max,
- sellBudget: state.sell.budget,
- sellIsRange: state.sell.isRange,
- start: start.toString(),
- end: end.toString(),
- },
- });
- };
-
return (
<>
Simulate Strategy
-
-
-
-
+ {!isError && (
+
+
+
+
+
+ )}
>
);
diff --git a/src/pages/simulator/overlapping/index.tsx b/src/pages/simulator/overlapping/index.tsx
new file mode 100644
index 000000000..eb28e7a39
--- /dev/null
+++ b/src/pages/simulator/overlapping/index.tsx
@@ -0,0 +1,125 @@
+import { calculateOverlappingPrices } from '@bancor/carbon-sdk/strategy-management';
+import { useNavigate } from '@tanstack/react-router';
+import { Button } from 'components/common/button';
+import { CreateOverlappingStrategy } from 'components/simulator/input/overlapping/CreateOverlappingStrategy';
+import { SimInputChart } from 'components/simulator/input/SimInputChart';
+import { useSimulatorOverlappingInput } from 'hooks/useSimulatorOverlappingInput';
+import { useGetTokenPriceHistory } from 'libs/queries/extApi/tokenPrice';
+import { simulatorInputOverlappingRoute } from 'libs/routing/routes/sim';
+import { defaultEnd, defaultStart } from 'pages/simulator/index';
+import { FormEvent, useEffect } from 'react';
+import { roundSearchParam } from 'utils/helpers';
+
+export const SimulatorInputOverlappingPage = () => {
+ const searchState = simulatorInputOverlappingRoute.useSearch();
+
+ const { dispatch, state, bounds } = useSimulatorOverlappingInput({
+ searchState,
+ });
+
+ const { data, isLoading, isError } = useGetTokenPriceHistory({
+ baseToken: searchState.baseToken,
+ quoteToken: searchState.quoteToken,
+ start: searchState.start,
+ end: searchState.end,
+ });
+
+ useEffect(() => {
+ if (searchState.sellMax) return;
+ dispatch('baseToken', searchState.baseToken);
+ dispatch('quoteToken', searchState.quoteToken);
+ dispatch('spread', '1');
+ dispatch('buyMax', '', true);
+ dispatch('buyMin', '', true);
+ dispatch('sellMax', '', true);
+ dispatch('sellMin', '', true);
+ dispatch('sellBudget', '');
+ dispatch('sellBudgetError', '');
+ dispatch('sellPriceError', '');
+ dispatch('buyBudget', '');
+ dispatch('buyBudgetError', '');
+ dispatch('buyPriceError', '');
+ }, [
+ dispatch,
+ searchState.baseToken,
+ searchState.quoteToken,
+ searchState.sellMax,
+ ]);
+
+ const noBudget = Number(state.buy.budget) + Number(state.sell.budget) <= 0;
+ const noBudgetText =
+ !isError && noBudget && 'Please add Sell and/or Buy budgets';
+ const loadingText = isLoading && 'Loading price history...';
+ const priceError = state.buy.priceError || state.sell.priceError;
+ const btnDisabled = isLoading || isError || noBudget || !!priceError;
+
+ const navigate = useNavigate();
+
+ const submit = (e: FormEvent) => {
+ e.preventDefault();
+ if (isLoading || isError || noBudget) return;
+ const start = state.start ?? defaultStart();
+ const end = state.end ?? defaultEnd();
+
+ const { buyPriceMarginal, sellPriceMarginal } = calculateOverlappingPrices(
+ state.buy.min,
+ state.sell.max,
+ data[0].open.toString(),
+ state.spread
+ );
+
+ navigate({
+ to: '/simulate/result',
+ search: {
+ baseToken: state.baseToken?.address || '',
+ quoteToken: state.quoteToken?.address || '',
+ buyMin: roundSearchParam(state.buy.min),
+ buyMax: roundSearchParam(state.buy.max),
+ buyBudget: roundSearchParam(state.buy.budget),
+ buyMarginal: roundSearchParam(buyPriceMarginal),
+ buyIsRange: true,
+ sellMin: roundSearchParam(state.sell.min),
+ sellMax: roundSearchParam(state.sell.max),
+ sellBudget: roundSearchParam(state.sell.budget),
+ sellMarginal: roundSearchParam(sellPriceMarginal),
+ sellIsRange: true,
+ start: start.toString(),
+ end: end.toString(),
+ type: 'overlapping',
+ spread: state.spread,
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/src/pages/simulator/recurring/index.tsx b/src/pages/simulator/recurring/index.tsx
new file mode 100644
index 000000000..776f83546
--- /dev/null
+++ b/src/pages/simulator/recurring/index.tsx
@@ -0,0 +1,171 @@
+import { useNavigate } from '@tanstack/react-router';
+import { Button } from 'components/common/button';
+import { SimInputChart } from 'components/simulator/input/SimInputChart';
+import { SimInputRecurring } from 'components/simulator/input/SimInputRecurring';
+import { useSimulatorInput } from 'hooks/useSimulatorInput';
+import {
+ useCompareTokenPrice,
+ useGetTokenPriceHistory,
+} from 'libs/queries/extApi/tokenPrice';
+import { StrategyDirection } from 'libs/routing';
+import { simulatorInputRecurringRoute } from 'libs/routing/routes/sim';
+import { SafeDecimal } from 'libs/safedecimal';
+import { defaultEnd, defaultStart } from 'pages/simulator/index';
+import { FormEvent, useCallback, useEffect, useState } from 'react';
+
+export const SimulatorInputRecurringPage = () => {
+ const searchState = simulatorInputRecurringRoute.useSearch();
+
+ const { dispatch, state, bounds } = useSimulatorInput({
+ searchState,
+ });
+
+ const [initBuyRange, setInitBuyRange] = useState(true);
+ const [initSellRange, setInitSellRange] = useState(true);
+ const { data, isLoading, isError } = useGetTokenPriceHistory({
+ baseToken: searchState.baseToken,
+ quoteToken: searchState.quoteToken,
+ start: searchState.start,
+ end: searchState.end,
+ });
+ const marketPrice = useCompareTokenPrice(
+ state.baseToken?.address,
+ state.quoteToken?.address
+ );
+
+ const handleDefaultValues = useCallback(
+ (type: StrategyDirection) => {
+ const init = type === 'buy' ? initBuyRange : initSellRange;
+ const setInit = type === 'buy' ? setInitBuyRange : setInitSellRange;
+
+ if (!marketPrice || !init) return;
+ setInit(false);
+
+ if (!(!state[type].max && !state[type].min)) {
+ return;
+ }
+
+ const operation = type === 'buy' ? 'minus' : 'plus';
+
+ const multiplierMax = type === 'buy' ? 0.1 : 0.2;
+ const multiplierMin = type === 'buy' ? 0.2 : 0.1;
+
+ const max = new SafeDecimal(marketPrice)
+ [operation](marketPrice * multiplierMax)
+ .toFixed();
+
+ const min = new SafeDecimal(marketPrice)
+ [operation](marketPrice * multiplierMin)
+ .toFixed();
+
+ if (state[type].isRange) {
+ dispatch(`${type}Max`, max);
+ dispatch(`${type}Min`, min);
+ } else {
+ const value = type === 'buy' ? max : min;
+ dispatch(`${type}Max`, value);
+ dispatch(`${type}Min`, value);
+ }
+ },
+ [
+ dispatch,
+ initBuyRange,
+ initSellRange,
+ marketPrice,
+ setInitBuyRange,
+ setInitSellRange,
+ state,
+ ]
+ );
+
+ useEffect(() => {
+ handleDefaultValues('buy');
+ handleDefaultValues('sell');
+ }, [handleDefaultValues]);
+
+ useEffect(() => {
+ if (initBuyRange || initSellRange) return;
+ dispatch('baseToken', searchState.baseToken);
+ dispatch('quoteToken', searchState.quoteToken);
+ dispatch('sellMax', '');
+ dispatch('sellMin', '');
+ dispatch('sellBudget', '');
+ dispatch('sellBudgetError', '');
+ dispatch('sellPriceError', '');
+ dispatch('sellIsRange', true);
+ dispatch('buyMax', '');
+ dispatch('buyMin', '');
+ dispatch('buyBudget', '');
+ dispatch('buyBudgetError', '');
+ dispatch('buyPriceError', '');
+ dispatch('buyIsRange', true);
+ setInitBuyRange(true);
+ setInitSellRange(true);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dispatch, searchState.baseToken, searchState.quoteToken]);
+
+ const noBudget = Number(state.buy.budget) + Number(state.sell.budget) <= 0;
+ const noBudgetText =
+ !isError && noBudget && 'Please add Sell and/or Buy budgets';
+ const loadingText = isLoading && 'Loading price history...';
+ const priceError = state.buy.priceError || state.sell.priceError;
+ const btnDisabled = isLoading || isError || noBudget || !!priceError;
+
+ const navigate = useNavigate();
+
+ const submit = (e: FormEvent) => {
+ e.preventDefault();
+ if (isLoading || isError || noBudget) return;
+ const start = state.start ?? defaultStart();
+ const end = state.end ?? defaultEnd();
+
+ navigate({
+ to: '/simulate/result',
+ search: {
+ baseToken: state.baseToken?.address || '',
+ quoteToken: state.quoteToken?.address || '',
+ buyMin: state.buy.min,
+ buyMax: state.buy.max,
+ buyBudget: state.buy.budget,
+ buyIsRange: state.buy.isRange,
+ sellMin: state.sell.min,
+ sellMax: state.sell.max,
+ sellBudget: state.sell.budget,
+ sellIsRange: state.sell.isRange,
+ start: start.toString(),
+ end: end.toString(),
+ type: 'recurring',
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
diff --git a/src/pages/simulator/result/index.tsx b/src/pages/simulator/result/index.tsx
index c7385ac7f..fac1a72f0 100644
--- a/src/pages/simulator/result/index.tsx
+++ b/src/pages/simulator/result/index.tsx
@@ -9,7 +9,7 @@ import { THREE_SECONDS_IN_MS } from 'utils/time';
export const SimulatorResultPage = () => {
const { status, isSuccess, start, ...ctx } = useSimulator();
- const simulationType = 'recurring';
+ const simulationType = ctx.search.type;
const handleAnimationStart = useCallback(() => {
if (!isSuccess || ['running', 'ended', 'paused'].includes(status)) {
@@ -27,17 +27,51 @@ export const SimulatorResultPage = () => {
return (
-
-
-
-
- Simulate Strategy
-
+ {simulationType === 'recurring' && (
+
+
+
+
+ Simulate Strategy
+
+ )}
+ {simulationType === 'overlapping' && (
+
+
+
+
+ Simulate Strategy
+
+ )}
(endpoint: string, params: Object = {}): Promise => {
const api = lsService.getItem('carbonApi') || config.carbonApi;
const url = new URL(api + endpoint);
for (const [key, value] of Object.entries(params)) {
- url.searchParams.set(key, value);
+ value !== 'undefined' && url.searchParams.set(key, value);
}
const response = await fetch(url);
const result = await response.json();
@@ -80,12 +80,8 @@ const carbonApi = {
},
getSimulator: async (
params: SimulatorAPIParams
- ): Promise => {
- return get('simulate-create-strategy', {
- ...params,
- baseBudget: params.sellBudget,
- quoteBudget: params.buyBudget,
- });
+ ): Promise => {
+ return get('simulator/create', params);
},
getActivity: async (params: QueryActivityParams) => {
return get('activity', params);
diff --git a/src/utils/helpers/schema.ts b/src/utils/helpers/schema.ts
index a17372cfc..959143da5 100644
--- a/src/utils/helpers/schema.ts
+++ b/src/utils/helpers/schema.ts
@@ -7,12 +7,12 @@ export const identity = (value: string) => value;
export const toBoolean =
(fallback: boolean = false) =>
(value: string = '') => {
- return value ? Boolean(value) : fallback;
+ return value ? value : fallback;
};
export const toString =
(fallback: string = '') =>
(value: string = '') => {
- return value ? String(value) : fallback;
+ return value ? value : fallback;
};
export const toNumber =
(fallback: number = 0) =>
@@ -22,7 +22,7 @@ export const toNumber =
export const toArray =
(fallback: string[] = []) =>
(value: string = '') => {
- return value ? value.split(',') : fallback;
+ return value ? (value as unknown as string[]) : fallback;
};
export const toLiteral =
(literals: T[], fallback: T) =>
@@ -32,7 +32,7 @@ export const toLiteral =
export const toDate =
(fallback?: Date) =>
(value: string = '') => {
- return value ? new Date(value) : fallback;
+ return value ? value : fallback;
};
export type SearchParams = Partial<{
[key in keyof T]: string;