Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 91 additions & 49 deletions packages/common/src/atoms/edit-position-atoms.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Atom, Registry, Result } from "@effect-atom/atom-react";
import { Effect, Option } from "effect";
import { Registry, Result } from "@effect-atom/atom-react";
import { Effect, flow, Match, Option } from "effect";
import { defined } from "effect/Match";
import type {
TPOrSLConfiguration,
TPOrSLOption,
TPOrSLSettings,
} from "../components/molecules/tp-sl-dialog";
import type { WalletConnected } from "../domain/wallet";
Expand All @@ -15,59 +15,101 @@ import { runtimeAtom } from "../services/runtime";
import { actionAtom } from "./actions-atoms";
import { selectedProviderAtom } from "./providers-atoms";

export const tpSlArgument = (tpOrSL: TPOrSLConfiguration) =>
Option.some(tpOrSL).pipe(
Option.filterMap((v) =>
v.triggerPrice !== null && v.option !== null
? Option.some(v.triggerPrice)
: Option.none(),
),
Option.getOrUndefined,
);
export const tpSlArgument = flow(
Option.liftPredicate(
(
tpOrSL: TPOrSLConfiguration,
): tpOrSL is TPOrSLConfiguration & { triggerPrice: number } =>
tpOrSL.triggerPrice !== null && tpOrSL.option !== null,
),
Option.map((v) => v.triggerPrice),
Option.getOrUndefined,
);

export const editSLTPAtom = Atom.family((actionType: TPOrSLOption) =>
runtimeAtom.fn(
Effect.fn(function* ({
position,
wallet,
tpOrSLSettings,
}: {
position: PositionDto;
wallet: WalletConnected;
tpOrSLSettings: TPOrSLSettings;
}) {
const client = yield* ApiClientService;
const registry = yield* Registry.AtomRegistry;
export const editSLTPAtom = runtimeAtom.fn(
Effect.fn(function* ({
position,
wallet,
tpOrSLSettings,
stopLossOrderId,
takeProfitOrderId,
}: {
position: PositionDto;
wallet: WalletConnected;
tpOrSLSettings: TPOrSLSettings;
stopLossOrderId?: string;
takeProfitOrderId?: string;
}) {
const client = yield* ApiClientService;
const registry = yield* Registry.AtomRegistry;

const selectedProvider = registry
.get(selectedProviderAtom)
.pipe(Result.getOrElse(() => null));
const selectedProvider = registry
.get(selectedProviderAtom)
.pipe(Result.getOrElse(() => null));

if (!selectedProvider) {
return yield* Effect.dieMessage("No selected provider");
}
if (!selectedProvider) {
return yield* Effect.dieMessage("No selected provider");
}

const newStopLossPrice: ArgumentsDto["stopLossPrice"] = tpSlArgument(
tpOrSLSettings.stopLoss,
);
const newStopLossPrice: ArgumentsDto["stopLossPrice"] = tpSlArgument(
tpOrSLSettings.stopLoss,
);

const newTakeProfitPrice: ArgumentsDto["takeProfitPrice"] = tpSlArgument(
tpOrSLSettings.takeProfit,
);
const newTakeProfitPrice: ArgumentsDto["takeProfitPrice"] = tpSlArgument(
tpOrSLSettings.takeProfit,
);

const action = yield* client.ActionsControllerExecuteAction({
providerId: selectedProvider.id,
address: wallet.currentAccount.address,
action: actionType,
const actionArgs = Match.value({
newStopLossPrice,
newTakeProfitPrice,
}).pipe(
Match.withReturnType<{
action: "setTpAndSl" | "takeProfit" | "stopLoss";
args: ArgumentsDto;
} | null>(),
Match.when(
{ newStopLossPrice: defined, newTakeProfitPrice: defined },
(v) => ({
action: "setTpAndSl",
args: {
stopLossPrice: v.newStopLossPrice,
takeProfitPrice: v.newTakeProfitPrice,
...(stopLossOrderId && { stopLossOrderId }),
...(takeProfitOrderId && { takeProfitOrderId }),
},
}),
),
Match.when({ newTakeProfitPrice: defined }, () => ({
action: "takeProfit",
args: {
takeProfitPrice: newTakeProfitPrice,
...(takeProfitOrderId && { orderId: takeProfitOrderId }),
},
})),
Match.when({ newStopLossPrice: defined }, () => ({
action: "stopLoss",
args: {
marketId: position.marketId,
...(actionType === "stopLoss"
? { stopLossPrice: newStopLossPrice }
: { takeProfitPrice: newTakeProfitPrice }),
stopLossPrice: newStopLossPrice,
...(stopLossOrderId && { orderId: stopLossOrderId }),
},
});
})),
Match.orElse(() => null),
);

registry.set(actionAtom, action);
}),
),
if (!actionArgs) {
return yield* Effect.dieMessage("No TP/SL settings provided");
}

const action = yield* client.ActionsControllerExecuteAction({
providerId: selectedProvider.id,
address: wallet.currentAccount.address,
action: actionArgs.action,
args: {
marketId: position.marketId,
...actionArgs.args,
},
});

registry.set(actionAtom, action);
}),
);
50 changes: 2 additions & 48 deletions packages/common/src/atoms/position-pending-actions-atom.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,13 @@
import { Registry, Result } from "@effect-atom/atom-react";
import { Effect } from "effect";
import type {
TPOrSLOption,
TPOrSLSettings,
} from "../components/molecules/tp-sl-dialog";
import type { WalletAccount, WalletConnected } from "../domain/wallet";
import type { WalletConnected } from "../domain/wallet";
import { ApiClientService } from "../services/api-client";
import type { PositionDto } from "../services/api-client/api-schemas";
import { runtimeAtom } from "../services/runtime";
import { actionAtom } from "./actions-atoms";
import { tpSlArgument } from "./edit-position-atoms";
import { selectedProviderAtom } from "./providers-atoms";

export const editSLOrTPAtom = runtimeAtom.fn(
Effect.fn(function* ({
position,
walletAddress,
tpOrSLSettings,
actionType,
}: {
position: PositionDto;
walletAddress: WalletAccount["address"];
tpOrSLSettings: TPOrSLSettings;
actionType: TPOrSLOption;
}) {
const client = yield* ApiClientService;
const registry = yield* Registry.AtomRegistry;

const selectedProvider = registry
.get(selectedProviderAtom)
.pipe(Result.getOrElse(() => null));

if (!selectedProvider) {
return yield* Effect.dieMessage("No selected provider");
}

const newStopLossPrice = tpSlArgument(tpOrSLSettings.stopLoss);

const newTakeProfitPrice = tpSlArgument(tpOrSLSettings.takeProfit);

const action = yield* client.ActionsControllerExecuteAction({
providerId: selectedProvider.id,
address: walletAddress,
action: actionType,
args: {
marketId: position.marketId,
...(actionType === "stopLoss"
? { stopLossPrice: newStopLossPrice }
: { takeProfitPrice: newTakeProfitPrice }),
},
});

registry.set(actionAtom, action);
}),
);
export type UpdateMarginMode = "add" | "remove";

export const updateLeverageAtom = runtimeAtom.fn(
Effect.fn(function* ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,16 @@ function formatTransactionType(type: string): string {
OPEN_POSITION: "Open Position",
CLOSE_POSITION: "Close Position",
UPDATE_LEVERAGE: "Update Leverage",
UPDATE_MARGIN: "Update Margin",
STOP_LOSS: "Update Stop Loss",
TAKE_PROFIT: "Update Take Profit",
CANCEL_ORDER: "Cancel Order",
FUND: "Fund Account",
WITHDRAW: "Withdraw",
SET_TP_AND_SL: "Set TP and SL",
};

return typeMap[type] || type;
return typeMap[type] || formatSnakeCase(type.toLowerCase());
}

function getErrorDescription(error: SignTransactionsState["error"]): string {
Expand Down
18 changes: 9 additions & 9 deletions packages/common/src/components/molecules/toggle-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,37 @@ import { ToggleGroup as BaseToggleGroup } from "@base-ui/react/toggle-group";
import type { ComponentProps } from "react";
import { cn } from "../../lib/utils";

export interface ToggleOption {
value: string;
export interface ToggleOption<T extends string = string> {
value: T;
label: string;
}

export interface ToggleGroupProps
export interface ToggleGroupProps<T extends string = string>
extends Omit<
ComponentProps<typeof BaseToggleGroup>,
"value" | "onValueChange"
> {
options: ToggleOption[];
value: string;
onValueChange: (value: string) => void;
options: ToggleOption<T>[];
value: T;
onValueChange: (value: T) => void;
variant?: "dark" | "light" | "compact";
}

export function ToggleGroup({
export function ToggleGroup<T extends string>({
options,
value,
onValueChange,
variant = "dark",
className,
...props
}: ToggleGroupProps) {
}: ToggleGroupProps<T>) {
return (
<BaseToggleGroup
value={[value]}
onValueChange={(values) => {
const newValue = values[values.length - 1];
if (newValue) {
onValueChange(newValue);
onValueChange(newValue as T);
}
}}
className={cn(
Expand Down
17 changes: 5 additions & 12 deletions packages/common/src/hooks/use-edit-position.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { useAtomSet, useAtomValue } from "@effect-atom/atom-react";
import { useAtom } from "@effect-atom/atom-react";
import { editSLTPAtom } from "../atoms/edit-position-atoms";
import { updateLeverageAtom } from "../atoms/position-pending-actions-atom";

export const useEditSLTP = () => {
const editTPResult = useAtomValue(editSLTPAtom("takeProfit"));
const editTP = useAtomSet(editSLTPAtom("takeProfit"));

const editSLResult = useAtomValue(editSLTPAtom("stopLoss"));
const editSL = useAtomSet(editSLTPAtom("stopLoss"));
const [editSLTPResult, editSLTP] = useAtom(editSLTPAtom);

return {
editTPResult,
editTP,
editSLResult,
editSL,
editSLTPResult,
editSLTP,
};
};

export const useUpdateLeverage = () => {
const updateLeverageResult = useAtomValue(updateLeverageAtom);
const updateLeverage = useAtomSet(updateLeverageAtom);
const [updateLeverageResult, updateLeverage] = useAtom(updateLeverageAtom);

return {
updateLeverageResult,
Expand Down
35 changes: 20 additions & 15 deletions packages/common/src/hooks/use-position-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ const UpdateLeverageSchema = Schema.Struct({
}),
});

const TpSlSchema = Schema.Struct({
type: Schema.Literal("stopLoss", "takeProfit"),
const SetTpAndSlSchema = Schema.Struct({
type: Schema.Literal("setTpAndSl"),
args: Schema.Struct({
marketId: Schema.String,
orderId: Schema.optional(Schema.String),
stopLossOrderId: Schema.optional(Schema.String),
takeProfitOrderId: Schema.optional(Schema.String),
}),
});

const PendingActionSchema = Schema.Union(UpdateLeverageSchema, TpSlSchema);
const PendingActionSchema = Schema.Union(
UpdateLeverageSchema,
SetTpAndSlSchema,
);

export const usePositionActions = (position: PositionDto) => {
return position.pendingActions.reduce(
export const getPositionActions = (
pendingActions: PositionDto["pendingActions"],
) => {
return pendingActions.reduce(
(acc, pa) => {
const decoded = Schema.decodeUnknownOption(PendingActionSchema)(pa).pipe(
Option.getOrNull,
Expand All @@ -31,24 +37,23 @@ export const usePositionActions = (position: PositionDto) => {
Match.when({ type: "updateLeverage" }, (v) => {
acc.updateLeverage = v;
}),
Match.when({ type: "stopLoss" }, (v) => {
acc.stopLoss = v;
}),
Match.when({ type: "takeProfit" }, (v) => {
acc.takeProfit = v;
Match.when({ type: "setTpAndSl" }, (v) => {
acc.setTpAndSl = v;
}),
);

return acc;
},
{
updateLeverage: null,
stopLoss: null,
takeProfit: null,
setTpAndSl: null,
} as {
updateLeverage: typeof UpdateLeverageSchema.Type | null;
stopLoss: typeof TpSlSchema.Type | null;
takeProfit: typeof TpSlSchema.Type | null;
setTpAndSl: typeof SetTpAndSlSchema.Type | null;
},
);
};

export const usePositionActions = (position: PositionDto) => {
return getPositionActions(position.pendingActions);
};
Loading
Loading