From db451d0dd8736b5b29e49a5e7ae4d347c679c302 Mon Sep 17 00:00:00 2001 From: tony Date: Thu, 16 Apr 2026 12:25:19 +0700 Subject: [PATCH] short sell all --- .../src/api/state-machine.ts | 42 ++++++++++++++----- .../src/services/position-service.ts | 7 +++- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/apps/long-short-backend/src/api/state-machine.ts b/apps/long-short-backend/src/api/state-machine.ts index a5bc093..b5f27a5 100644 --- a/apps/long-short-backend/src/api/state-machine.ts +++ b/apps/long-short-backend/src/api/state-machine.ts @@ -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 = { @@ -976,7 +977,10 @@ export namespace StateMachine { */ export const handleShortSell = async (options: HandleBuildTxOptions): Promise => { 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"); @@ -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; }; /** @@ -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 => { - 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(); @@ -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(), }; } @@ -2325,7 +2334,7 @@ export namespace StateMachine { * This is the final closing step — position becomes CLOSED */ export const waitingShortWithdraw = async (options: WaitingOptions): Promise => { - const { txHash, userAddress, cardanoscanProvider } = options; + const { marketConfig, txHash, userAddress, cardanoscanProvider, kupoService } = options; const txFoundOnChain = await cardanoscanProvider.findTransactionByHash( userAddress, @@ -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(), }; } } @@ -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 */ @@ -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, }; } diff --git a/apps/long-short-backend/src/services/position-service.ts b/apps/long-short-backend/src/services/position-service.ts index 15e602c..3f66827 100644 --- a/apps/long-short-backend/src/services/position-service.ts +++ b/apps/long-short-backend/src/services/position-service.ts @@ -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) @@ -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, @@ -793,6 +794,10 @@ export class PositionService { positionId: position.id, orderType: StateMachine.ShortOrderType.SHORT_WITHDRAW, }, + { + positionId: position.id, + orderType: StateMachine.ShortOrderType.SHORT_SELL_ALL, + }, ]); }