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
42 changes: 31 additions & 11 deletions apps/long-short-backend/src/api/state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export namespace StateMachine {
SHORT_WITHDRAW_FRACTION = "SHORT_WITHDRAW_FRACTION",
SHORT_BUY_FREED = "SHORT_BUY_FREED",
SHORT_WITHDRAW = "SHORT_WITHDRAW",
SHORT_SELL_ALL = "SHORT_SELL_ALL",
}

export type BuiltResult = {
Expand Down Expand Up @@ -976,7 +977,10 @@ export namespace StateMachine {
*/
export const handleShortSell = async (options: HandleBuildTxOptions): Promise<BuiltResult> => {
const { order, marketConfig, userAddress, networkEnv, utxos } = options;
invariant(order.orderType === ShortOrderType.SHORT_SELL, "Invalid order type for handleShortSell");
invariant(
order.orderType === ShortOrderType.SHORT_SELL || order.orderType === ShortOrderType.SHORT_SELL_ALL,
"Invalid order type for handleShortSell",
);
invariant(order.assetIn, "assetIn is required for SHORT_SELL order");
invariant(order.amountIn, "amountIn is required for SHORT_SELL order");
invariant(order.assetOut, "assetOut is required for SHORT_SELL order");
Expand Down Expand Up @@ -1722,6 +1726,8 @@ export namespace StateMachine {
* Stored as a string to avoid BigInt serialisation issues.
*/
accumulatedAda?: string;
/** Kupo service for querying user wallet balance (SHORT_WITHDRAW leftover check) */
kupoService: KupoService;
};

/**
Expand Down Expand Up @@ -2149,11 +2155,12 @@ export namespace StateMachine {
};

/**
* Wait for SHORT_SELL order output to be spent (consumed by DEX)
* This is the final opening step — position becomes OPEN
* Wait for SHORT_SELL / SHORT_SELL_ALL order output to be spent (consumed by DEX)
* - SHORT_SELL: final opening step — position becomes OPEN
* - SHORT_SELL_ALL: final closing step — position becomes CLOSED
*/
export const waitingShortSell = async (options: WaitingOptions): Promise<WaitingResult> => {
const { txHash, orderOutputIndex, userAddress, cardanoscanProvider } = options;
const { txHash, orderOutputIndex, userAddress, cardanoscanProvider, orderType } = options;
invariant(orderOutputIndex !== undefined, "orderOutputIndex is required for waitingShortSell");

const userAddressHex = userAddress.toHex();
Expand All @@ -2167,14 +2174,16 @@ export namespace StateMachine {
);

if (spendingTx) {
// SHORT_SELL sells asset B for ADA, so we look for ADA in outputs
// SHORT_SELL / SHORT_SELL_ALL sell asset B for ADA, so we look for ADA in outputs
for (const output of spendingTx.outputs) {
if (output.address === userAddressHex) {
const amountOut = BigInt(output.value);
const positionStatus =
orderType === ShortOrderType.SHORT_SELL_ALL ? PositionStatus.CLOSED : PositionStatus.OPEN;
return {
isConfirmed: true,
isFinal: true,
positionStatus: PositionStatus.OPEN,
positionStatus,
amountOut: amountOut.toString(),
};
}
Expand Down Expand Up @@ -2325,7 +2334,7 @@ export namespace StateMachine {
* This is the final closing step — position becomes CLOSED
*/
export const waitingShortWithdraw = async (options: WaitingOptions): Promise<WaitingResult> => {
const { txHash, userAddress, cardanoscanProvider } = options;
const { marketConfig, txHash, userAddress, cardanoscanProvider, kupoService } = options;

const txFoundOnChain = await cardanoscanProvider.findTransactionByHash(
userAddress,
Expand All @@ -2340,12 +2349,21 @@ export namespace StateMachine {
// For SHORT_WITHDRAW, we withdraw ADA — look for ADA value in outputs
for (const output of txFoundOnChain.outputs) {
if (output.address === userAddressHex) {
const amountOut = BigInt(output.value);
// Query wallet assetB balance — always transition to SHORT_SELL_ALL (pre-created in closePosition)
const walletBalance = await kupoService.getBalanceOfPubKeyAddress(userAddress.bech32);
const leftoverB = walletBalance.get(marketConfig.assetB);
logger.info("waitingShortWithdraw: leftover assetB", {
leftoverB: leftoverB.toString(),
assetB: marketConfig.assetB.toString(),
});

return {
isConfirmed: true,
isFinal: true,
positionStatus: PositionStatus.CLOSED,
amountOut: amountOut.toString(),
nextOrderType: ShortOrderType.SHORT_SELL_ALL,
assetIn: marketConfig.assetB.toString(),
amountIn: leftoverB.toString(),
assetOut: marketConfig.assetA.toString(),
amountOut: leftoverB.toString(),
};
}
}
Expand Down Expand Up @@ -2378,6 +2396,7 @@ export namespace StateMachine {
[ShortOrderType.SHORT_WITHDRAW_FRACTION]: handleShortWithdrawFraction,
[ShortOrderType.SHORT_BUY_FREED]: handleShortBuyFreed,
[ShortOrderType.SHORT_WITHDRAW]: handleShortWithdraw,
[ShortOrderType.SHORT_SELL_ALL]: handleShortSell,
};

/** Map of order types to their waiting functions */
Expand All @@ -2402,5 +2421,6 @@ export namespace StateMachine {
[ShortOrderType.SHORT_WITHDRAW_FRACTION]: waitingShortWithdrawFraction,
[ShortOrderType.SHORT_BUY_FREED]: waitingShortBuyFreed,
[ShortOrderType.SHORT_WITHDRAW]: waitingShortWithdraw,
[ShortOrderType.SHORT_SELL_ALL]: waitingShortSell,
};
}
7 changes: 6 additions & 1 deletion apps/long-short-backend/src/services/position-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export class PositionService {
orderOutputIndex: waitingOrder.createdTxIndex ?? 0,
assetOut: Asset.fromString(waitingOrder.assetOut),
positionAmountIn: position.amountIn,
kupoService: this.kupoService,
};

// For fractional repay flows, pass the original debt (stored in amountOut during creation)
Expand Down Expand Up @@ -776,7 +777,7 @@ export class PositionService {
throw new Error("SHORT_SELL order not found or amountOut not set");
}

// Create 3 SHORT closing orders: buy asset B → repay loan → withdraw ADA
// Create 4 SHORT closing orders: buy asset B → repay loan → withdraw ADA → sell leftover assetB
await OrderRepository.createOrders(trx, [
{
positionId: position.id,
Expand All @@ -793,6 +794,10 @@ export class PositionService {
positionId: position.id,
orderType: StateMachine.ShortOrderType.SHORT_WITHDRAW,
},
{
positionId: position.id,
orderType: StateMachine.ShortOrderType.SHORT_SELL_ALL,
},
]);
}

Expand Down
Loading