@@ -51,79 +51,86 @@ func (k Keeper) FinalizeOutbound(ctx context.Context, utxId string, outbound typ
5151 sdkCtx := sdk .UnwrapSDKContext (ctx )
5252
5353 if ! obs .Success {
54- return k .handleFailedOutbound (sdkCtx , utxId , outbound )
54+ return k .handleFailedOutbound (sdkCtx , utxId , outbound , obs )
5555 }
5656
5757 return k .handleSuccessfulOutbound (sdkCtx , utxId , outbound , obs )
5858}
5959
60- // handleFailedOutbound mints back the bridged tokens to the revert recipient.
61- func (k Keeper ) handleFailedOutbound (ctx sdk.Context , utxId string , outbound types.OutboundTx ) error {
62- // Only refund for funds-related tx types
63- if outbound .TxType != types .TxType_FUNDS && outbound .TxType != types .TxType_GAS_AND_PAYLOAD &&
64- outbound .TxType != types .TxType_FUNDS_AND_PAYLOAD {
65- return nil
66- }
60+ // handleFailedOutbound mints back the bridged tokens to the revert recipient,
61+ // then attempts to refund any excess gas (gasFee - gasFeeUsed) just like a
62+ // successful outbound would. Both operations are recorded on the outbound.
63+ func (k Keeper ) handleFailedOutbound (ctx sdk.Context , utxId string , outbound types.OutboundTx , obs * types.OutboundObservation ) error {
64+ // Only revert bridged funds for funds-related tx types
65+ if outbound .TxType == types .TxType_FUNDS || outbound .TxType == types .TxType_GAS_AND_PAYLOAD ||
66+ outbound .TxType == types .TxType_FUNDS_AND_PAYLOAD {
67+
68+ // Decide revert recipient safely
69+ recipient := outbound .Sender
70+ if outbound .RevertInstructions != nil &&
71+ outbound .RevertInstructions .FundRecipient != "" {
72+ recipient = outbound .RevertInstructions .FundRecipient
73+ }
6774
68- // Decide refund recipient safely
69- recipient := outbound .Sender
70- if outbound . RevertInstructions != nil &&
71- outbound . RevertInstructions . FundRecipient != "" {
72- recipient = outbound . RevertInstructions . FundRecipient
73- }
75+ amount := new (big. Int )
76+ amount , ok := amount . SetString ( outbound .Amount , 10 )
77+ if ! ok {
78+ return fmt . Errorf ( "invalid amount: %s" , outbound . Amount )
79+ }
80+ receipt , err := k . CallPRC20Deposit ( ctx , common . HexToAddress ( outbound . Prc20AssetAddr ), common . HexToAddress ( recipient ), amount )
7481
75- // Mint tokens back
76- amount := new (big.Int )
77- amount , ok := amount .SetString (outbound .Amount , 10 )
78- if ! ok {
79- return fmt .Errorf ("invalid amount: %s" , outbound .Amount )
82+ pcTx := types.PCTx {
83+ Sender : outbound .Sender ,
84+ BlockHeight : uint64 (ctx .BlockHeight ()),
85+ }
86+ if err != nil {
87+ pcTx .Status = "FAILED"
88+ pcTx .ErrorMsg = err .Error ()
89+ } else {
90+ pcTx .TxHash = receipt .Hash
91+ pcTx .GasUsed = receipt .GasUsed
92+ pcTx .Status = "SUCCESS"
93+ }
94+ outbound .PcRevertExecution = & pcTx
8095 }
81- receipt , err := k .CallPRC20Deposit (ctx , common .HexToAddress (outbound .Prc20AssetAddr ), common .HexToAddress (recipient ), amount )
8296
83- // Update outbound status
8497 outbound .OutboundStatus = types .Status_REVERTED
8598
86- pcTx := types.PCTx {
87- TxHash : "" ,
88- Sender : outbound .Sender ,
89- GasUsed : 0 ,
90- BlockHeight : uint64 (ctx .BlockHeight ()),
91- }
92-
93- if err != nil {
94- pcTx .Status = "FAILED"
95- pcTx .ErrorMsg = err .Error ()
96- } else {
97- pcTx .TxHash = receipt .Hash
98- pcTx .GasUsed = receipt .GasUsed
99- pcTx .Status = "SUCCESS"
100- }
101-
102- outbound .PcRevertExecution = & pcTx
99+ // Refund excess gas regardless of tx type — gas was consumed on the external
100+ // chain whether the execution succeeded or failed.
101+ k .applyGasRefund (ctx , & outbound , obs )
103102
104103 return k .UpdateOutbound (ctx , utxId , outbound )
105104}
106105
107106// handleSuccessfulOutbound refunds unused gas fee when gasFee > gasFeeUsed.
108107func (k Keeper ) handleSuccessfulOutbound (ctx sdk.Context , utxId string , outbound types.OutboundTx , obs * types.OutboundObservation ) error {
109- // Only attempt refund if gas_fee_used is populated and gas token is known
108+ k .applyGasRefund (ctx , & outbound , obs )
109+ return k .UpdateOutbound (ctx , utxId , outbound )
110+ }
111+
112+ // applyGasRefund computes the excess gas (gasFee - gasFeeUsed) and, if positive,
113+ // calls UniversalCore refundUnusedGas. The result is recorded in outbound.PcRefundExecution.
114+ // It is called for both successful and failed outbounds — gas is consumed on the
115+ // external chain regardless of execution outcome.
116+ func (k Keeper ) applyGasRefund (ctx sdk.Context , outbound * types.OutboundTx , obs * types.OutboundObservation ) {
110117 if obs .GasFeeUsed == "" || outbound .GasFee == "" || outbound .GasToken == "" {
111- return k . UpdateOutbound ( ctx , utxId , outbound )
118+ return
112119 }
113120
114121 gasFee := new (big.Int )
115122 if _ , ok := gasFee .SetString (outbound .GasFee , 10 ); ! ok {
116- return k . UpdateOutbound ( ctx , utxId , outbound )
123+ return
117124 }
118125
119126 gasFeeUsed := new (big.Int )
120127 if _ , ok := gasFeeUsed .SetString (obs .GasFeeUsed , 10 ); ! ok {
121- return k . UpdateOutbound ( ctx , utxId , outbound )
128+ return
122129 }
123130
124- // No refund needed
131+ // No excess gas to refund
125132 if gasFee .Cmp (gasFeeUsed ) <= 0 {
126- return k . UpdateOutbound ( ctx , utxId , outbound )
133+ return
127134 }
128135
129136 refundAmount := new (big.Int ).Sub (gasFee , gasFeeUsed )
@@ -144,7 +151,6 @@ func (k Keeper) handleSuccessfulOutbound(ctx sdk.Context, utxId string, outbound
144151 // Step 1: try refund with swap (gasToken → PC native)
145152 fee , swapErr := k .GetDefaultFeeTierForToken (ctx , gasToken )
146153 var swapFallbackReason string
147- var receipt interface { GetHash () string }
148154
149155 if swapErr == nil {
150156 quote , quoteErr := k .getSwapQuoteForRefund (ctx , gasToken , fee , refundAmount )
@@ -158,7 +164,7 @@ func (k Keeper) handleSuccessfulOutbound(ctx sdk.Context, utxId string, outbound
158164 refundPcTx .GasUsed = resp .GasUsed
159165 refundPcTx .Status = "SUCCESS"
160166 outbound .PcRefundExecution = refundPcTx
161- return k . UpdateOutbound ( ctx , utxId , outbound )
167+ return
162168 }
163169 swapFallbackReason = fmt .Sprintf ("swap refund failed: %s" , err .Error ())
164170 } else {
@@ -168,10 +174,8 @@ func (k Keeper) handleSuccessfulOutbound(ctx sdk.Context, utxId string, outbound
168174 swapFallbackReason = fmt .Sprintf ("fee tier fetch failed: %s" , swapErr .Error ())
169175 }
170176
171- _ = receipt
172-
173177 // Step 2: fallback — refund without swap (deposit PRC20 directly to recipient)
174- ctx .Logger ().Error ("FinalizeOutbound : swap refund failed, falling back to no-swap" ,
178+ ctx .Logger ().Error ("applyGasRefund : swap refund failed, falling back to no-swap" ,
175179 "outbound_id" , outbound .Id ,
176180 "reason" , swapFallbackReason ,
177181 )
@@ -188,8 +192,6 @@ func (k Keeper) handleSuccessfulOutbound(ctx sdk.Context, utxId string, outbound
188192
189193 outbound .PcRefundExecution = refundPcTx
190194 outbound .RefundSwapError = swapFallbackReason
191-
192- return k .UpdateOutbound (ctx , utxId , outbound )
193195}
194196
195197// getSwapQuoteForRefund fetches a Uniswap quote for the gas token refund swap.
0 commit comments