@loafmarkets/ui 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +35 -30
- package/dist/index.d.ts +35 -30
- package/dist/index.js +230 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +230 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -56,6 +56,34 @@ interface PortfolioSummaryProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
56
56
|
}
|
|
57
57
|
declare const PortfolioSummary: React.ForwardRefExoticComponent<PortfolioSummaryProps & React.RefAttributes<HTMLDivElement>>;
|
|
58
58
|
|
|
59
|
+
type OrderbookSide = "ask" | "bid";
|
|
60
|
+
type OrderbookLevel = {
|
|
61
|
+
price: number;
|
|
62
|
+
amount: number;
|
|
63
|
+
depth?: number;
|
|
64
|
+
};
|
|
65
|
+
type OrderbookTrade = {
|
|
66
|
+
type: "buy" | "sell";
|
|
67
|
+
price: number;
|
|
68
|
+
amount: number;
|
|
69
|
+
time?: string;
|
|
70
|
+
};
|
|
71
|
+
interface OrderbookProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
72
|
+
asks: OrderbookLevel[];
|
|
73
|
+
bids: OrderbookLevel[];
|
|
74
|
+
midPrice: number;
|
|
75
|
+
midChangePercent?: number;
|
|
76
|
+
trades?: OrderbookTrade[];
|
|
77
|
+
priceLabel?: string;
|
|
78
|
+
amountLabel?: string;
|
|
79
|
+
precision?: number;
|
|
80
|
+
amountPrecision?: number;
|
|
81
|
+
defaultTab?: "orderbook" | "trades";
|
|
82
|
+
onTabChange?: (tab: "orderbook" | "trades") => void;
|
|
83
|
+
rightHeader?: React.ReactNode;
|
|
84
|
+
}
|
|
85
|
+
declare const Orderbook: React.ForwardRefExoticComponent<OrderbookProps & React.RefAttributes<HTMLDivElement>>;
|
|
86
|
+
|
|
59
87
|
type HousePositionPendingOrder = {
|
|
60
88
|
type: "buy" | "sell";
|
|
61
89
|
tokens: number;
|
|
@@ -74,6 +102,10 @@ type HousePositionSliderOrderPayload = {
|
|
|
74
102
|
targetTokens: number;
|
|
75
103
|
targetValue: number;
|
|
76
104
|
};
|
|
105
|
+
type HousePositionOrderbook = {
|
|
106
|
+
bids?: OrderbookLevel[];
|
|
107
|
+
asks?: OrderbookLevel[];
|
|
108
|
+
};
|
|
77
109
|
interface HousePositionSliderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
78
110
|
tokenId: string;
|
|
79
111
|
tokenSymbol: string;
|
|
@@ -83,37 +115,10 @@ interface HousePositionSliderProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
83
115
|
tokensHeld: number;
|
|
84
116
|
pendingOrders?: HousePositionPendingOrder[];
|
|
85
117
|
defaultOrderType?: "market" | "limit";
|
|
118
|
+
orderbook?: HousePositionOrderbook;
|
|
86
119
|
onConfirmOrder?: (payload: HousePositionSliderOrderPayload) => void;
|
|
87
120
|
}
|
|
88
|
-
declare function HousePositionSlider({ tokenId, tokenSymbol, totalTokens, currentPrice, availableCash, tokensHeld, pendingOrders, defaultOrderType, onConfirmOrder, className, ...props }: HousePositionSliderProps): react_jsx_runtime.JSX.Element;
|
|
89
|
-
|
|
90
|
-
type OrderbookSide = "ask" | "bid";
|
|
91
|
-
type OrderbookLevel = {
|
|
92
|
-
price: number;
|
|
93
|
-
amount: number;
|
|
94
|
-
depth?: number;
|
|
95
|
-
};
|
|
96
|
-
type OrderbookTrade = {
|
|
97
|
-
type: "buy" | "sell";
|
|
98
|
-
price: number;
|
|
99
|
-
amount: number;
|
|
100
|
-
time?: string;
|
|
101
|
-
};
|
|
102
|
-
interface OrderbookProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
103
|
-
asks: OrderbookLevel[];
|
|
104
|
-
bids: OrderbookLevel[];
|
|
105
|
-
midPrice: number;
|
|
106
|
-
midChangePercent?: number;
|
|
107
|
-
trades?: OrderbookTrade[];
|
|
108
|
-
priceLabel?: string;
|
|
109
|
-
amountLabel?: string;
|
|
110
|
-
precision?: number;
|
|
111
|
-
amountPrecision?: number;
|
|
112
|
-
defaultTab?: "orderbook" | "trades";
|
|
113
|
-
onTabChange?: (tab: "orderbook" | "trades") => void;
|
|
114
|
-
rightHeader?: React.ReactNode;
|
|
115
|
-
}
|
|
116
|
-
declare const Orderbook: React.ForwardRefExoticComponent<OrderbookProps & React.RefAttributes<HTMLDivElement>>;
|
|
121
|
+
declare function HousePositionSlider({ tokenId, tokenSymbol, totalTokens, currentPrice, availableCash, tokensHeld, pendingOrders, defaultOrderType, orderbook, onConfirmOrder, className, ...props }: HousePositionSliderProps): react_jsx_runtime.JSX.Element;
|
|
117
122
|
|
|
118
123
|
type PropertyTourProps = Omit<React.ComponentPropsWithoutRef<typeof Card>, "title"> & {
|
|
119
124
|
title: string;
|
|
@@ -268,4 +273,4 @@ declare const PropertySubheader: React.ForwardRefExoticComponent<React.HTMLAttri
|
|
|
268
273
|
actions?: PropertySubheaderAction[];
|
|
269
274
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
270
275
|
|
|
271
|
-
export { Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, type HousePositionPendingOrder, HousePositionSlider, type HousePositionSliderOrderPayload, type HousePositionSliderProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, PropertyHeroHeader, type PropertyHeroHeaderProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, badgeVariants, buttonVariants };
|
|
276
|
+
export { Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, type HousePositionSliderOrderPayload, type HousePositionSliderProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, PropertyHeroHeader, type PropertyHeroHeaderProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, badgeVariants, buttonVariants };
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,34 @@ interface PortfolioSummaryProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
56
56
|
}
|
|
57
57
|
declare const PortfolioSummary: React.ForwardRefExoticComponent<PortfolioSummaryProps & React.RefAttributes<HTMLDivElement>>;
|
|
58
58
|
|
|
59
|
+
type OrderbookSide = "ask" | "bid";
|
|
60
|
+
type OrderbookLevel = {
|
|
61
|
+
price: number;
|
|
62
|
+
amount: number;
|
|
63
|
+
depth?: number;
|
|
64
|
+
};
|
|
65
|
+
type OrderbookTrade = {
|
|
66
|
+
type: "buy" | "sell";
|
|
67
|
+
price: number;
|
|
68
|
+
amount: number;
|
|
69
|
+
time?: string;
|
|
70
|
+
};
|
|
71
|
+
interface OrderbookProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
72
|
+
asks: OrderbookLevel[];
|
|
73
|
+
bids: OrderbookLevel[];
|
|
74
|
+
midPrice: number;
|
|
75
|
+
midChangePercent?: number;
|
|
76
|
+
trades?: OrderbookTrade[];
|
|
77
|
+
priceLabel?: string;
|
|
78
|
+
amountLabel?: string;
|
|
79
|
+
precision?: number;
|
|
80
|
+
amountPrecision?: number;
|
|
81
|
+
defaultTab?: "orderbook" | "trades";
|
|
82
|
+
onTabChange?: (tab: "orderbook" | "trades") => void;
|
|
83
|
+
rightHeader?: React.ReactNode;
|
|
84
|
+
}
|
|
85
|
+
declare const Orderbook: React.ForwardRefExoticComponent<OrderbookProps & React.RefAttributes<HTMLDivElement>>;
|
|
86
|
+
|
|
59
87
|
type HousePositionPendingOrder = {
|
|
60
88
|
type: "buy" | "sell";
|
|
61
89
|
tokens: number;
|
|
@@ -74,6 +102,10 @@ type HousePositionSliderOrderPayload = {
|
|
|
74
102
|
targetTokens: number;
|
|
75
103
|
targetValue: number;
|
|
76
104
|
};
|
|
105
|
+
type HousePositionOrderbook = {
|
|
106
|
+
bids?: OrderbookLevel[];
|
|
107
|
+
asks?: OrderbookLevel[];
|
|
108
|
+
};
|
|
77
109
|
interface HousePositionSliderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
78
110
|
tokenId: string;
|
|
79
111
|
tokenSymbol: string;
|
|
@@ -83,37 +115,10 @@ interface HousePositionSliderProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
83
115
|
tokensHeld: number;
|
|
84
116
|
pendingOrders?: HousePositionPendingOrder[];
|
|
85
117
|
defaultOrderType?: "market" | "limit";
|
|
118
|
+
orderbook?: HousePositionOrderbook;
|
|
86
119
|
onConfirmOrder?: (payload: HousePositionSliderOrderPayload) => void;
|
|
87
120
|
}
|
|
88
|
-
declare function HousePositionSlider({ tokenId, tokenSymbol, totalTokens, currentPrice, availableCash, tokensHeld, pendingOrders, defaultOrderType, onConfirmOrder, className, ...props }: HousePositionSliderProps): react_jsx_runtime.JSX.Element;
|
|
89
|
-
|
|
90
|
-
type OrderbookSide = "ask" | "bid";
|
|
91
|
-
type OrderbookLevel = {
|
|
92
|
-
price: number;
|
|
93
|
-
amount: number;
|
|
94
|
-
depth?: number;
|
|
95
|
-
};
|
|
96
|
-
type OrderbookTrade = {
|
|
97
|
-
type: "buy" | "sell";
|
|
98
|
-
price: number;
|
|
99
|
-
amount: number;
|
|
100
|
-
time?: string;
|
|
101
|
-
};
|
|
102
|
-
interface OrderbookProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
103
|
-
asks: OrderbookLevel[];
|
|
104
|
-
bids: OrderbookLevel[];
|
|
105
|
-
midPrice: number;
|
|
106
|
-
midChangePercent?: number;
|
|
107
|
-
trades?: OrderbookTrade[];
|
|
108
|
-
priceLabel?: string;
|
|
109
|
-
amountLabel?: string;
|
|
110
|
-
precision?: number;
|
|
111
|
-
amountPrecision?: number;
|
|
112
|
-
defaultTab?: "orderbook" | "trades";
|
|
113
|
-
onTabChange?: (tab: "orderbook" | "trades") => void;
|
|
114
|
-
rightHeader?: React.ReactNode;
|
|
115
|
-
}
|
|
116
|
-
declare const Orderbook: React.ForwardRefExoticComponent<OrderbookProps & React.RefAttributes<HTMLDivElement>>;
|
|
121
|
+
declare function HousePositionSlider({ tokenId, tokenSymbol, totalTokens, currentPrice, availableCash, tokensHeld, pendingOrders, defaultOrderType, orderbook, onConfirmOrder, className, ...props }: HousePositionSliderProps): react_jsx_runtime.JSX.Element;
|
|
117
122
|
|
|
118
123
|
type PropertyTourProps = Omit<React.ComponentPropsWithoutRef<typeof Card>, "title"> & {
|
|
119
124
|
title: string;
|
|
@@ -268,4 +273,4 @@ declare const PropertySubheader: React.ForwardRefExoticComponent<React.HTMLAttri
|
|
|
268
273
|
actions?: PropertySubheaderAction[];
|
|
269
274
|
} & React.RefAttributes<HTMLDivElement>>;
|
|
270
275
|
|
|
271
|
-
export { Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, type HousePositionPendingOrder, HousePositionSlider, type HousePositionSliderOrderPayload, type HousePositionSliderProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, PropertyHeroHeader, type PropertyHeroHeaderProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, badgeVariants, buttonVariants };
|
|
276
|
+
export { Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, type CardContentProps, CardDescription, type CardDescriptionProps, CardFooter, type CardFooterProps, CardHeader, type CardHeaderProps, type CardProps, CardTitle, type CardTitleProps, type HousePositionOrderbook, type HousePositionPendingOrder, HousePositionSlider, type HousePositionSliderOrderPayload, type HousePositionSliderProps, Orderbook, type OrderbookLevel, type OrderbookProps, type OrderbookSide, type OrderbookTrade, PortfolioSummary, type PortfolioSummaryProps, PriceChart, type PriceChartCandle, type PriceChartProps, type PriceChartRange, PropertyHeroHeader, type PropertyHeroHeaderProps, type PropertyNewsItem, type PropertyNewsType, PropertyNewsUpdates, type PropertyNewsUpdatesProps, PropertySubheader, type PropertySubheaderAction, type PropertySubheaderProps, type PropertySubheaderTab, PropertyTour, type PropertyTourProps, type YourOrder, type YourOrderSide, YourOrders, type YourOrdersProps, badgeVariants, buttonVariants };
|
package/dist/index.js
CHANGED
|
@@ -234,6 +234,66 @@ var PortfolioSummary = React5__namespace.forwardRef(
|
|
|
234
234
|
PortfolioSummary.displayName = "PortfolioSummary";
|
|
235
235
|
var clamp = (v, min, max) => Math.min(max, Math.max(min, v));
|
|
236
236
|
var fmt0 = (v) => Math.abs(v).toLocaleString(void 0, { maximumFractionDigits: 0 });
|
|
237
|
+
var normalizeLevels = (levels = []) => levels.filter((level) => Number.isFinite(level.price) && level.price > 0 && Number.isFinite(level.amount) && level.amount > 0);
|
|
238
|
+
var estimateMarketBuyFromUsd = (levels = [], usdAmount) => {
|
|
239
|
+
if (!Number.isFinite(usdAmount) || usdAmount <= 0) return { tokens: 0, value: 0, avgPrice: null };
|
|
240
|
+
const asks = normalizeLevels(levels).sort((a, b) => a.price - b.price);
|
|
241
|
+
let remainingUsd = usdAmount;
|
|
242
|
+
let tokensFilled = 0;
|
|
243
|
+
let spent = 0;
|
|
244
|
+
for (const level of asks) {
|
|
245
|
+
if (remainingUsd <= 0) break;
|
|
246
|
+
const levelValueCapacity = level.amount * level.price;
|
|
247
|
+
const usdToSpend = Math.min(remainingUsd, levelValueCapacity);
|
|
248
|
+
const tokensFromLevel = usdToSpend / level.price;
|
|
249
|
+
tokensFilled += tokensFromLevel;
|
|
250
|
+
spent += usdToSpend;
|
|
251
|
+
remainingUsd -= usdToSpend;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
tokens: tokensFilled,
|
|
255
|
+
value: spent,
|
|
256
|
+
avgPrice: tokensFilled > 0 ? spent / tokensFilled : null
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
var estimateMarketBuyFromTokens = (levels = [], tokenAmount) => {
|
|
260
|
+
if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) return { tokens: 0, value: 0, avgPrice: null };
|
|
261
|
+
const asks = normalizeLevels(levels).sort((a, b) => a.price - b.price);
|
|
262
|
+
let remainingTokens = tokenAmount;
|
|
263
|
+
let tokensFilled = 0;
|
|
264
|
+
let spent = 0;
|
|
265
|
+
for (const level of asks) {
|
|
266
|
+
if (remainingTokens <= 0) break;
|
|
267
|
+
const tokensFromLevel = Math.min(remainingTokens, level.amount);
|
|
268
|
+
spent += tokensFromLevel * level.price;
|
|
269
|
+
tokensFilled += tokensFromLevel;
|
|
270
|
+
remainingTokens -= tokensFromLevel;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
tokens: tokensFilled,
|
|
274
|
+
value: spent,
|
|
275
|
+
avgPrice: tokensFilled > 0 ? spent / tokensFilled : null
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
var estimateMarketSellFromTokens = (levels = [], tokenAmount) => {
|
|
279
|
+
if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) return { tokens: 0, value: 0, avgPrice: null };
|
|
280
|
+
const bids = normalizeLevels(levels).sort((a, b) => b.price - a.price);
|
|
281
|
+
let remainingTokens = tokenAmount;
|
|
282
|
+
let tokensFilled = 0;
|
|
283
|
+
let received = 0;
|
|
284
|
+
for (const level of bids) {
|
|
285
|
+
if (remainingTokens <= 0) break;
|
|
286
|
+
const tokensFromLevel = Math.min(remainingTokens, level.amount);
|
|
287
|
+
received += tokensFromLevel * level.price;
|
|
288
|
+
tokensFilled += tokensFromLevel;
|
|
289
|
+
remainingTokens -= tokensFromLevel;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
tokens: tokensFilled,
|
|
293
|
+
value: received,
|
|
294
|
+
avgPrice: tokensFilled > 0 ? received / tokensFilled : null
|
|
295
|
+
};
|
|
296
|
+
};
|
|
237
297
|
function HousePositionSlider({
|
|
238
298
|
tokenId,
|
|
239
299
|
tokenSymbol,
|
|
@@ -243,6 +303,7 @@ function HousePositionSlider({
|
|
|
243
303
|
tokensHeld,
|
|
244
304
|
pendingOrders = [],
|
|
245
305
|
defaultOrderType = "market",
|
|
306
|
+
orderbook,
|
|
246
307
|
onConfirmOrder,
|
|
247
308
|
className,
|
|
248
309
|
...props
|
|
@@ -256,45 +317,103 @@ function HousePositionSlider({
|
|
|
256
317
|
const [orderType, setOrderType] = React5__namespace.useState(defaultOrderType);
|
|
257
318
|
const [limitPrice, setLimitPrice] = React5__namespace.useState(currentPrice);
|
|
258
319
|
const [limitPriceInput, setLimitPriceInput] = React5__namespace.useState(currentPrice.toFixed(2));
|
|
320
|
+
const [limitPriceDirty, setLimitPriceDirty] = React5__namespace.useState(false);
|
|
259
321
|
const [ownershipInput, setOwnershipInput] = React5__namespace.useState("");
|
|
260
322
|
const [tokenAmountInput, setTokenAmountInput] = React5__namespace.useState("");
|
|
261
323
|
const houseRef = React5__namespace.useRef(null);
|
|
324
|
+
const asks = orderbook?.asks ?? [];
|
|
325
|
+
const bids = orderbook?.bids ?? [];
|
|
262
326
|
React5__namespace.useEffect(() => {
|
|
327
|
+
if (orderType !== "limit") return;
|
|
328
|
+
if (limitPriceDirty) return;
|
|
263
329
|
setLimitPrice(currentPrice);
|
|
264
330
|
setLimitPriceInput(currentPrice.toFixed(2));
|
|
265
|
-
}, [currentPrice]);
|
|
331
|
+
}, [currentPrice, limitPriceDirty, orderType]);
|
|
266
332
|
const effectivePrice = orderType === "limit" ? limitPrice : currentPrice;
|
|
267
|
-
const holdingsValue = tokensHeld * effectivePrice;
|
|
268
|
-
const totalCapacity = holdingsValue + availableCash;
|
|
269
333
|
const pendingBuyValue = pendingOrders.filter((o) => o.type === "buy").reduce((s, o) => s + o.value, 0);
|
|
270
334
|
const pendingSellTokens = pendingOrders.filter((o) => o.type === "sell").reduce((s, o) => s + Math.abs(o.tokens), 0);
|
|
271
335
|
const effectiveAvailableCash = Math.max(0, availableCash - pendingBuyValue);
|
|
272
336
|
const effectiveTokensHeld = Math.max(0, tokensHeld - pendingSellTokens);
|
|
273
|
-
const
|
|
337
|
+
const holdingsValue = tokensHeld * effectivePrice;
|
|
338
|
+
const sliderHoldingsValue = effectiveTokensHeld * effectivePrice;
|
|
339
|
+
const sliderTotalCapacity = sliderHoldingsValue + effectiveAvailableCash;
|
|
340
|
+
const baselinePct = sliderTotalCapacity <= 0 ? 0 : sliderHoldingsValue / sliderTotalCapacity * 100;
|
|
274
341
|
let deltaTokens = 0;
|
|
275
342
|
let deltaValue = 0;
|
|
343
|
+
let marketAvgPrice = null;
|
|
276
344
|
let targetTokens = tokensHeld;
|
|
277
345
|
let targetValue = holdingsValue;
|
|
346
|
+
const limitPriceSafe = limitPrice > 0 ? limitPrice : currentPrice || 1;
|
|
278
347
|
if (orderMode === "buy") {
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
348
|
+
if (orderType === "market") {
|
|
349
|
+
if (buyTrackingMode === "tokens") {
|
|
350
|
+
const desiredTokens = Math.max(0, deltaTokensBuy);
|
|
351
|
+
const result = estimateMarketBuyFromTokens(asks, desiredTokens);
|
|
352
|
+
deltaTokens = result.tokens;
|
|
353
|
+
deltaValue = result.value;
|
|
354
|
+
marketAvgPrice = result.avgPrice;
|
|
355
|
+
} else {
|
|
356
|
+
const notional = Math.min(Math.max(0, deltaDollars), effectiveAvailableCash);
|
|
357
|
+
const result = estimateMarketBuyFromUsd(asks, notional);
|
|
358
|
+
deltaTokens = result.tokens;
|
|
359
|
+
deltaValue = result.value;
|
|
360
|
+
marketAvgPrice = result.avgPrice;
|
|
361
|
+
}
|
|
282
362
|
} else {
|
|
283
|
-
|
|
284
|
-
|
|
363
|
+
if (buyTrackingMode === "tokens") {
|
|
364
|
+
deltaTokens = deltaTokensBuy;
|
|
365
|
+
deltaValue = deltaTokensBuy * limitPriceSafe;
|
|
366
|
+
} else {
|
|
367
|
+
const notional = Math.min(Math.max(0, deltaDollars), effectiveAvailableCash);
|
|
368
|
+
deltaValue = notional;
|
|
369
|
+
deltaTokens = notional / limitPriceSafe;
|
|
370
|
+
}
|
|
285
371
|
}
|
|
286
372
|
} else if (orderMode === "sell") {
|
|
287
|
-
|
|
288
|
-
|
|
373
|
+
if (orderType === "market") {
|
|
374
|
+
const tokensToSell = Math.abs(deltaTokensSell);
|
|
375
|
+
const result = estimateMarketSellFromTokens(bids, tokensToSell);
|
|
376
|
+
deltaTokens = -result.tokens;
|
|
377
|
+
deltaValue = -result.value;
|
|
378
|
+
marketAvgPrice = result.avgPrice;
|
|
379
|
+
} else {
|
|
380
|
+
deltaTokens = deltaTokensSell;
|
|
381
|
+
deltaValue = deltaTokensSell * limitPriceSafe;
|
|
382
|
+
}
|
|
289
383
|
}
|
|
290
384
|
targetTokens = tokensHeld + deltaTokens;
|
|
291
385
|
targetValue = targetTokens * effectivePrice;
|
|
292
|
-
const
|
|
386
|
+
const plannedDeltaValue = (() => {
|
|
387
|
+
if (orderMode === "buy") {
|
|
388
|
+
if (buyTrackingMode === "dollars") {
|
|
389
|
+
const notional = Math.min(Math.max(0, deltaDollars), effectiveAvailableCash);
|
|
390
|
+
return notional;
|
|
391
|
+
}
|
|
392
|
+
const tokensPlanned = Math.max(0, deltaTokensBuy);
|
|
393
|
+
const referencePrice = orderType === "market" ? currentPrice || limitPriceSafe : limitPriceSafe;
|
|
394
|
+
return Math.min(tokensPlanned * referencePrice, effectiveAvailableCash);
|
|
395
|
+
}
|
|
396
|
+
if (orderMode === "sell") {
|
|
397
|
+
const tokensToSell = Math.abs(Math.min(0, deltaTokensSell));
|
|
398
|
+
const sellValue = tokensToSell * effectivePrice;
|
|
399
|
+
return -Math.min(sellValue, sliderHoldingsValue);
|
|
400
|
+
}
|
|
401
|
+
return 0;
|
|
402
|
+
})();
|
|
403
|
+
const sliderTargetValue = clamp(sliderHoldingsValue + plannedDeltaValue, 0, sliderTotalCapacity);
|
|
404
|
+
const targetPct = sliderTotalCapacity <= 0 ? 0 : sliderTargetValue / sliderTotalCapacity * 100;
|
|
293
405
|
const isIncrease = orderMode === "buy";
|
|
294
406
|
const hasChange = orderMode !== "none" && (Math.abs(deltaTokens) > 1e-3 || Math.abs(deltaValue) > 0.01);
|
|
295
407
|
const currentOwnership = totalTokens <= 0 ? 0 : tokensHeld / totalTokens * 100;
|
|
296
408
|
const targetOwnership = totalTokens <= 0 ? 0 : targetTokens / totalTokens * 100;
|
|
297
409
|
const estFeeTokens = Math.abs(deltaValue) * 5e-3 / (effectivePrice || 1);
|
|
410
|
+
const resetOrder = React5__namespace.useCallback(() => {
|
|
411
|
+
setOrderMode("none");
|
|
412
|
+
setBuyTrackingMode("dollars");
|
|
413
|
+
setDeltaDollars(0);
|
|
414
|
+
setDeltaTokensBuy(0);
|
|
415
|
+
setDeltaTokensSell(0);
|
|
416
|
+
}, []);
|
|
298
417
|
const updateOrderFromTargetValue = React5__namespace.useCallback(
|
|
299
418
|
(newTargetValue) => {
|
|
300
419
|
const newDeltaValue = newTargetValue - holdingsValue;
|
|
@@ -315,13 +434,9 @@ function HousePositionSlider({
|
|
|
315
434
|
setDeltaTokensBuy(0);
|
|
316
435
|
return;
|
|
317
436
|
}
|
|
318
|
-
|
|
319
|
-
setBuyTrackingMode("dollars");
|
|
320
|
-
setDeltaDollars(0);
|
|
321
|
-
setDeltaTokensBuy(0);
|
|
322
|
-
setDeltaTokensSell(0);
|
|
437
|
+
resetOrder();
|
|
323
438
|
},
|
|
324
|
-
[effectiveAvailableCash, effectivePrice, effectiveTokensHeld, holdingsValue, tokensHeld]
|
|
439
|
+
[effectiveAvailableCash, effectivePrice, effectiveTokensHeld, holdingsValue, resetOrder, tokensHeld]
|
|
325
440
|
);
|
|
326
441
|
const updateOrderFromOwnership = React5__namespace.useCallback(
|
|
327
442
|
(newOwnershipPercent) => {
|
|
@@ -350,13 +465,47 @@ function HousePositionSlider({
|
|
|
350
465
|
setDeltaTokensBuy(0);
|
|
351
466
|
return;
|
|
352
467
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
468
|
+
resetOrder();
|
|
469
|
+
},
|
|
470
|
+
[effectiveAvailableCash, effectivePrice, effectiveTokensHeld, resetOrder]
|
|
471
|
+
);
|
|
472
|
+
const updateOrderFromSlider = React5__namespace.useCallback(
|
|
473
|
+
(pct) => {
|
|
474
|
+
const normalized = (pct - 50) / 50;
|
|
475
|
+
const magnitude = Math.min(Math.abs(normalized), 1);
|
|
476
|
+
if (magnitude < 0.02) {
|
|
477
|
+
resetOrder();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (normalized > 0) {
|
|
481
|
+
const notional = clamp(magnitude * effectiveAvailableCash, 0, effectiveAvailableCash);
|
|
482
|
+
if (notional <= 0) {
|
|
483
|
+
resetOrder();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
setOrderMode("buy");
|
|
487
|
+
setBuyTrackingMode("dollars");
|
|
488
|
+
setDeltaDollars(notional);
|
|
489
|
+
setDeltaTokensBuy(0);
|
|
490
|
+
setDeltaTokensSell(0);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (normalized < 0) {
|
|
494
|
+
const tokensToSell = clamp(magnitude * effectiveTokensHeld, 0, effectiveTokensHeld);
|
|
495
|
+
if (tokensToSell <= 0) {
|
|
496
|
+
resetOrder();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
setOrderMode("sell");
|
|
500
|
+
setBuyTrackingMode("dollars");
|
|
501
|
+
setDeltaTokensSell(-tokensToSell);
|
|
502
|
+
setDeltaDollars(0);
|
|
503
|
+
setDeltaTokensBuy(0);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
resetOrder();
|
|
358
507
|
},
|
|
359
|
-
[effectiveAvailableCash,
|
|
508
|
+
[effectiveAvailableCash, effectiveTokensHeld, resetOrder]
|
|
360
509
|
);
|
|
361
510
|
const handleDragAtClientY = React5__namespace.useCallback(
|
|
362
511
|
(clientY) => {
|
|
@@ -364,9 +513,9 @@ function HousePositionSlider({
|
|
|
364
513
|
const rect = houseRef.current.getBoundingClientRect();
|
|
365
514
|
const y = clientY - rect.top;
|
|
366
515
|
const pct = clamp(100 - y / rect.height * 100, 0, 100);
|
|
367
|
-
|
|
516
|
+
updateOrderFromSlider(pct);
|
|
368
517
|
},
|
|
369
|
-
[
|
|
518
|
+
[updateOrderFromSlider]
|
|
370
519
|
);
|
|
371
520
|
const onMouseDown = (e) => {
|
|
372
521
|
e.preventDefault();
|
|
@@ -396,15 +545,21 @@ function HousePositionSlider({
|
|
|
396
545
|
document.addEventListener("touchend", onEnd);
|
|
397
546
|
};
|
|
398
547
|
const handleCancel = () => {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
548
|
+
resetOrder();
|
|
549
|
+
};
|
|
550
|
+
const handleOrderTypeSelection = (next) => {
|
|
551
|
+
setOrderType(next);
|
|
552
|
+
if (next === "limit") {
|
|
553
|
+
setLimitPriceDirty(false);
|
|
554
|
+
setLimitPrice(currentPrice);
|
|
555
|
+
setLimitPriceInput(currentPrice.toFixed(2));
|
|
556
|
+
} else {
|
|
557
|
+
setLimitPriceDirty(false);
|
|
558
|
+
}
|
|
404
559
|
};
|
|
405
560
|
const handleConfirm = () => {
|
|
406
561
|
if (!hasChange) return;
|
|
407
|
-
const priceToUse = orderType === "market" ? currentPrice : limitPrice;
|
|
562
|
+
const priceToUse = orderType === "market" ? marketAvgPrice ?? currentPrice : limitPrice;
|
|
408
563
|
onConfirmOrder?.({
|
|
409
564
|
side: isIncrease ? "buy" : "sell",
|
|
410
565
|
orderType,
|
|
@@ -429,6 +584,8 @@ function HousePositionSlider({
|
|
|
429
584
|
const showIncrease = targetPct > baselinePct;
|
|
430
585
|
const showDecrease = targetPct < baselinePct;
|
|
431
586
|
const valueLabel = orderType === "limit" ? `${tokenSymbol} Owned at Limit` : `${tokenSymbol} Owned`;
|
|
587
|
+
const deltaSign = deltaValue > 0 ? "+" : deltaValue < 0 ? "-" : "";
|
|
588
|
+
const percentMarkers = [100, 50, 25, 0];
|
|
432
589
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative flex w-full flex-col items-center gap-6 rounded-[12px] bg-black/20 px-8 pb-6 pt-12", className), ...props, children: [
|
|
433
590
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-4 top-4 text-[1.1rem] font-semibold tracking-[0.5px] text-white", children: "Place Order" }),
|
|
434
591
|
hasChange ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -442,46 +599,53 @@ function HousePositionSlider({
|
|
|
442
599
|
) : null,
|
|
443
600
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
444
601
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-xs uppercase tracking-[1px] text-[#888]", children: valueLabel }),
|
|
445
|
-
/* @__PURE__ */ jsxRuntime.
|
|
446
|
-
|
|
447
|
-
{
|
|
448
|
-
className: cn("text-[2rem] font-semibold", deltaValue >= 0 ? "text-[#0ecb81]" : "text-[#f6465d]"),
|
|
449
|
-
children: [
|
|
450
|
-
deltaValue > 0 ? "+" : deltaValue < 0 ? "-" : "",
|
|
451
|
-
"$",
|
|
452
|
-
fmt0(deltaValue)
|
|
453
|
-
]
|
|
454
|
-
}
|
|
455
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
456
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
457
|
-
"$",
|
|
458
|
-
fmt0(targetValue)
|
|
459
|
-
] }),
|
|
460
|
-
hasChange ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
602
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-center gap-3", children: [
|
|
603
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
461
604
|
"span",
|
|
462
605
|
{
|
|
463
606
|
className: cn(
|
|
464
|
-
"
|
|
607
|
+
"text-[2.2rem] font-semibold",
|
|
465
608
|
deltaValue >= 0 ? "text-[#0ecb81]" : "text-[#f6465d]"
|
|
466
609
|
),
|
|
467
610
|
children: [
|
|
468
|
-
|
|
611
|
+
deltaSign,
|
|
612
|
+
"$",
|
|
469
613
|
fmt0(deltaValue)
|
|
470
614
|
]
|
|
471
615
|
}
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
/* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "loaf-clip", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "10", width: "100", height: "140", rx: "8" }) }),
|
|
478
|
-
/* @__PURE__ */ jsxRuntime.jsxs("g", { clipPath: "url(#loaf-clip)", children: [
|
|
479
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: showDecrease ? targetFillY : baselineFillY, width: "100", height: showDecrease ? targetFillHeight : baselineFillHeight, fill: "rgba(220,175,120,0.7)" }),
|
|
480
|
-
showIncrease ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: targetFillY, width: "100", height: targetFillHeight - baselineFillHeight, fill: "rgba(14,203,129,0.35)" }) : null,
|
|
481
|
-
showDecrease ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: baselineFillY, width: "100", height: baselineFillHeight - targetFillHeight, fill: "rgba(246,70,93,0.35)" }) : null,
|
|
482
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: targetFillY, x2: "108", y2: targetFillY, stroke: showIncrease ? "#0ecb81" : showDecrease ? "#f6465d" : "rgba(234,217,162,1)", strokeWidth: "2", strokeLinecap: "round" })
|
|
616
|
+
),
|
|
617
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-lg font-semibold text-white/60", children: [
|
|
618
|
+
"$",
|
|
619
|
+
fmt0(targetValue)
|
|
620
|
+
] })
|
|
483
621
|
] })
|
|
484
|
-
] })
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
624
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[200px] flex-col justify-between text-xs text-white/40", children: percentMarkers.map((marker) => /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
625
|
+
marker,
|
|
626
|
+
"%"
|
|
627
|
+
] }, marker)) }),
|
|
628
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
629
|
+
"div",
|
|
630
|
+
{
|
|
631
|
+
ref: houseRef,
|
|
632
|
+
className: "h-[200px] w-[160px] select-none touch-none",
|
|
633
|
+
style: { cursor: "ns-resize" },
|
|
634
|
+
onMouseDown,
|
|
635
|
+
onTouchStart,
|
|
636
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 120 160", className: "h-full w-full overflow-visible", children: [
|
|
637
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "10", width: "100", height: "140", rx: "8", fill: "rgba(255,255,255,0.04)", stroke: "rgba(255,255,255,0.10)" }),
|
|
638
|
+
/* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "loaf-clip", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "10", width: "100", height: "140", rx: "8" }) }),
|
|
639
|
+
/* @__PURE__ */ jsxRuntime.jsxs("g", { clipPath: "url(#loaf-clip)", children: [
|
|
640
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: showDecrease ? targetFillY : baselineFillY, width: "100", height: showDecrease ? targetFillHeight : baselineFillHeight, fill: "rgba(220,175,120,0.7)" }),
|
|
641
|
+
showIncrease ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: targetFillY, width: "100", height: targetFillHeight - baselineFillHeight, fill: "rgba(14,203,129,0.35)" }) : null,
|
|
642
|
+
showDecrease ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: baselineFillY, width: "100", height: baselineFillHeight - targetFillHeight, fill: "rgba(246,70,93,0.35)" }) : null,
|
|
643
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: targetFillY, x2: "108", y2: targetFillY, stroke: showIncrease ? "#0ecb81" : showDecrease ? "#f6465d" : "rgba(234,217,162,1)", strokeWidth: "2", strokeLinecap: "round" })
|
|
644
|
+
] })
|
|
645
|
+
] })
|
|
646
|
+
}
|
|
647
|
+
)
|
|
648
|
+
] }),
|
|
485
649
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
486
650
|
"button",
|
|
487
651
|
{
|
|
@@ -531,7 +695,7 @@ function HousePositionSlider({
|
|
|
531
695
|
)
|
|
532
696
|
] })
|
|
533
697
|
] }),
|
|
534
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-2", children: [
|
|
698
|
+
orderType === "market" ? null : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-2", children: [
|
|
535
699
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/50", children: isIncrease ? "Buying" : "Selling" }),
|
|
536
700
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
537
701
|
orderMode === "buy" && buyTrackingMode === "dollars" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1 text-[#0ecb81]", children: "~" }) : null,
|
|
@@ -580,7 +744,7 @@ function HousePositionSlider({
|
|
|
580
744
|
"button",
|
|
581
745
|
{
|
|
582
746
|
type: "button",
|
|
583
|
-
onClick: () =>
|
|
747
|
+
onClick: () => handleOrderTypeSelection("market"),
|
|
584
748
|
className: cn(
|
|
585
749
|
"flex-1 rounded-[6px] px-3 py-2 text-[0.8rem] font-medium transition",
|
|
586
750
|
orderType === "market" ? "bg-[rgba(201,162,39,0.2)] text-[#C9A227]" : "text-white/50 hover:bg-white/5"
|
|
@@ -592,7 +756,7 @@ function HousePositionSlider({
|
|
|
592
756
|
"button",
|
|
593
757
|
{
|
|
594
758
|
type: "button",
|
|
595
|
-
onClick: () =>
|
|
759
|
+
onClick: () => handleOrderTypeSelection("limit"),
|
|
596
760
|
className: cn(
|
|
597
761
|
"flex-1 rounded-[6px] px-3 py-2 text-[0.8rem] font-medium transition",
|
|
598
762
|
orderType === "limit" ? "bg-[rgba(201,162,39,0.2)] text-[#C9A227]" : "text-white/50 hover:bg-white/5"
|
|
@@ -610,6 +774,7 @@ function HousePositionSlider({
|
|
|
610
774
|
onChange: (e) => {
|
|
611
775
|
const input = e.target.value;
|
|
612
776
|
if (input === "" || /^[0-9]*\.?[0-9]*$/.test(input)) {
|
|
777
|
+
setLimitPriceDirty(true);
|
|
613
778
|
setLimitPriceInput(input);
|
|
614
779
|
const num = Number.parseFloat(input);
|
|
615
780
|
if (Number.isFinite(num) && num > 0) setLimitPrice(num);
|