@@ -51,7 +51,10 @@ export default function SwapPage({ params }: { params: { id: string } }) {
5151 fetchTokens ( )
5252
5353 // Poll for updates every 10 seconds
54- const interval = setInterval ( fetchSwap , 10000 )
54+ const interval = setInterval ( ( ) => {
55+ fetchSwap ( )
56+ checkHTLCSpendStatus ( )
57+ } , 10000 )
5558 return ( ) => clearInterval ( interval )
5659 } , [ params . id ] )
5760
@@ -108,6 +111,140 @@ export default function SwapPage({ params }: { params: { id: string } }) {
108111 }
109112 }
110113
114+ const checkHTLCSpendStatus = async ( ) => {
115+ if ( ! swap || ! client ) return
116+
117+ try {
118+ const network = ( process . env . NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet' ) || 'testnet'
119+ const apiServer = network === 'mainnet'
120+ ? 'https://api-server.mintlayer.org/api/v2'
121+ : 'https://api-server-lovelace.mintlayer.org/api/v2'
122+
123+ let needsUpdate = false
124+ const updates : any = { }
125+ let creatorHtlcSpent = false
126+ let takerHtlcSpent = false
127+ let creatorHtlcSpendTxId : string | null = null
128+ let takerHtlcSpendTxId : string | null = null
129+
130+ // Check creator's ML HTLC
131+ if ( swap . creatorHtlcTxHash ) {
132+ try {
133+ const outputResponse = await fetch ( `${ apiServer } /transaction/${ swap . creatorHtlcTxHash } /output/0` )
134+ if ( outputResponse . ok ) {
135+ const outputData = await outputResponse . json ( )
136+ creatorHtlcSpent = outputData . spent === true
137+ if ( creatorHtlcSpent && outputData . spent_by ) {
138+ creatorHtlcSpendTxId = outputData . spent_by
139+ console . log ( 'Creator HTLC has been spent by:' , creatorHtlcSpendTxId )
140+ }
141+ }
142+ } catch ( error ) {
143+ console . error ( 'Error checking creator HTLC spend status:' , error )
144+ }
145+ }
146+
147+ // Check taker's ML HTLC
148+ if ( swap . takerHtlcTxHash ) {
149+ try {
150+ const outputResponse = await fetch ( `${ apiServer } /transaction/${ swap . takerHtlcTxHash } /output/0` )
151+ if ( outputResponse . ok ) {
152+ const outputData = await outputResponse . json ( )
153+ takerHtlcSpent = outputData . spent === true
154+ if ( takerHtlcSpent && outputData . spent_by ) {
155+ takerHtlcSpendTxId = outputData . spent_by
156+ console . log ( 'Taker HTLC has been spent by:' , takerHtlcSpendTxId )
157+ }
158+ }
159+ } catch ( error ) {
160+ console . error ( 'Error checking taker HTLC spend status:' , error )
161+ }
162+ }
163+
164+ // Determine status based on what's been spent
165+ if ( creatorHtlcSpent && takerHtlcSpent ) {
166+ // Both HTLCs spent = fully completed
167+ if ( swap . status !== 'fully_completed' ) {
168+ needsUpdate = true
169+ updates . status = 'fully_completed'
170+ }
171+ } else if ( creatorHtlcSpent || takerHtlcSpent ) {
172+ // One HTLC spent = first claim completed, need to extract secret
173+ if ( swap . status === 'fully_completed' ) {
174+ // Downgrade from fully_completed to completed if only one is actually spent
175+ needsUpdate = true
176+ updates . status = 'completed'
177+ console . log ( 'Downgrading status from fully_completed to completed - only one HTLC is spent' )
178+ } else if ( swap . status !== 'completed' ) {
179+ needsUpdate = true
180+ updates . status = 'completed'
181+ }
182+
183+ // Try to extract the secret from the claim transaction if we don't have it
184+ if ( ! swap . secret ) {
185+ try {
186+ const spendTxId = creatorHtlcSpent ? creatorHtlcSpendTxId : takerHtlcSpendTxId
187+ const htlcTxId = creatorHtlcSpent ? swap . creatorHtlcTxHash : swap . takerHtlcTxHash
188+
189+ if ( spendTxId && htlcTxId ) {
190+ console . log ( 'Attempting to extract secret from claim transaction:' , spendTxId )
191+
192+ // Fetch the claim transaction to get its hex
193+ const claimTxResponse = await fetch ( `${ apiServer } /transaction/${ spendTxId } ` )
194+ if ( claimTxResponse . ok ) {
195+ const claimTxData = await claimTxResponse . json ( )
196+ const claimTxHex = claimTxData . hex
197+
198+ if ( claimTxHex ) {
199+ // Extract the secret using the SDK
200+ const extractedSecret = await client . extractHtlcSecret ( {
201+ transaction_id : spendTxId ,
202+ transaction_hex : claimTxHex ,
203+ format : 'hex'
204+ } )
205+
206+ console . log ( 'Successfully extracted secret:' , extractedSecret )
207+ updates . secret = extractedSecret
208+ updates . claimTxHash = spendTxId
209+ updates . claimTxHex = claimTxHex
210+ needsUpdate = true
211+ }
212+ }
213+ }
214+ } catch ( error ) {
215+ console . error ( 'Error extracting secret from claim transaction:' , error )
216+ // Continue with status update even if secret extraction fails
217+ }
218+ }
219+ } else {
220+ // Neither HTLC is spent - downgrade status if needed
221+ if ( swap . status === 'completed' || swap . status === 'fully_completed' ) {
222+ needsUpdate = true
223+ // Determine appropriate status based on what HTLCs exist
224+ if ( swap . creatorHtlcTxHash && swap . takerHtlcTxHash ) {
225+ updates . status = 'in_progress'
226+ } else if ( swap . creatorHtlcTxHash || swap . takerHtlcTxHash ) {
227+ updates . status = 'htlc_created'
228+ }
229+ console . log ( 'Downgrading status - no HTLCs are actually spent on-chain' )
230+ }
231+ }
232+
233+ // Update swap if needed
234+ if ( needsUpdate ) {
235+ console . log ( 'Updating swap status based on spend status:' , updates )
236+ await fetch ( `/api/swaps/${ swap . id } ` , {
237+ method : 'POST' ,
238+ headers : { 'Content-Type' : 'application/json' } ,
239+ body : JSON . stringify ( updates )
240+ } )
241+ fetchSwap ( )
242+ }
243+ } catch ( error ) {
244+ console . error ( 'Error checking HTLC spend status:' , error )
245+ }
246+ }
247+
111248 const connectWallet = async ( ) => {
112249 try {
113250 if ( client ) {
@@ -889,7 +1026,7 @@ export default function SwapPage({ params }: { params: { id: string } }) {
8891026 case 'btc_htlc_created' : return 'BTC HTLC created, waiting for ML HTLC'
8901027 case 'both_htlcs_created' : return 'Both HTLCs created, ready to claim'
8911028 case 'in_progress' : return 'Claims in progress'
892- case 'completed' : return 'First claim completed, waiting for final claim'
1029+ case 'completed' : return 'First claim completed (secret revealed) , waiting for final claim'
8931030 case 'fully_completed' : return 'Atomic swap completed successfully'
8941031 case 'refunded' : return 'ML HTLC refunded due to timeout'
8951032 case 'btc_refunded' : return 'BTC HTLC refunded due to timeout'
@@ -935,7 +1072,7 @@ export default function SwapPage({ params }: { params: { id: string } }) {
9351072 </ button >
9361073 ) }
9371074 </ div >
938- < div className = "flex items-center space-x-4" >
1075+ < div className = "flex items-center space-x-4 flex-wrap " >
9391076 < span className = { `px-3 py-1 rounded-full text-sm font-medium ${ getStatusColor ( swap . status ) } ` } >
9401077 { swap . status . toUpperCase ( ) }
9411078 </ span >
@@ -945,6 +1082,15 @@ export default function SwapPage({ params }: { params: { id: string } }) {
9451082 Connected: { userAddress . slice ( 0 , 10 ) } ...
9461083 </ span >
9471084 ) }
1085+ { ( swap . status === 'in_progress' || swap . status === 'completed' || swap . status === 'both_htlcs_created' ) && (
1086+ < button
1087+ onClick = { checkHTLCSpendStatus }
1088+ className = "text-sm bg-gray-200 text-gray-700 px-3 py-1 rounded-md hover:bg-gray-300"
1089+ title = "Check if HTLCs have been claimed on-chain"
1090+ >
1091+ 🔄 Check Status
1092+ </ button >
1093+ ) }
9481094 </ div >
9491095 </ div >
9501096
@@ -1068,7 +1214,7 @@ export default function SwapPage({ params }: { params: { id: string } }) {
10681214
10691215 < div className = "flex items-center" >
10701216 < div className = { `w-4 h-4 rounded-full mr-3 ${
1071- ( swap . claimTxHash || swap . btcClaimTxId ) ? 'bg-green-500' : 'bg-gray-300'
1217+ ( swap . claimTxHash || swap . btcClaimTxId ) && swap . secret ? 'bg-green-500' : 'bg-gray-300'
10721218 } `} > </ div >
10731219 < span className = "text-sm" >
10741220 First claim completed (secret revealed)
@@ -1078,13 +1224,6 @@ export default function SwapPage({ params }: { params: { id: string } }) {
10781224 </ span >
10791225 </ div >
10801226
1081- < div className = "flex items-center" >
1082- < div className = { `w-4 h-4 rounded-full mr-3 ${
1083- swap . secret ? 'bg-green-500' : 'bg-gray-300'
1084- } `} > </ div >
1085- < span className = "text-sm" > Secret extracted</ span >
1086- </ div >
1087-
10881227 < div className = "flex items-center" >
10891228 < div className = { `w-4 h-4 rounded-full mr-3 ${
10901229 swap . status === 'fully_completed' ||
@@ -1236,74 +1375,84 @@ export default function SwapPage({ params }: { params: { id: string } }) {
12361375
12371376 { ( swap . status === 'completed' || swap . status === 'fully_completed' ) && (
12381377 < div className = "bg-green-50 p-4 rounded-md" >
1239- { ( swap . claimTxHash || swap . btcClaimTxId ) && (
1240- < div className = "space-y-3" >
1241- < p className = "text-green-800" >
1242- { swap . status === 'fully_completed'
1243- ? "🎉 Atomic swap completed successfully! Both parties have their tokens."
1244- : isUserCreator
1245- ? "You have claimed the taker's HTLC!"
1246- : "The creator has claimed your HTLC!"
1378+ < div className = "space-y-3" >
1379+ < p className = "text-green-800 font-semibold" >
1380+ { swap . status === 'fully_completed'
1381+ ? "🎉 Atomic swap completed successfully! Both parties have their tokens."
1382+ : "✅ First HTLC claimed! Secret has been revealed."
1383+ }
1384+ </ p >
1385+ { swap . status === 'completed' && (
1386+ < p className = "text-blue-800 text-sm" >
1387+ { isUserCreator
1388+ ? "You claimed the taker's HTLC. The taker can now use the revealed secret to claim your HTLC."
1389+ : "The creator claimed your HTLC and revealed the secret. You can now claim the creator's HTLC!"
12471390 }
12481391 </ p >
1392+ ) }
12491393
1250- { /* Show ML claim transaction if it exists */ }
1251- { swap . claimTxHash && (
1252- < p className = "text-sm text-green-700" >
1253- ML Claim Transaction: { swap . claimTxHash }
1254- </ p >
1255- ) }
1394+ { /* Show ML claim transaction if it exists */ }
1395+ { swap . claimTxHash && (
1396+ < p className = "text-sm text-green-700" >
1397+ ML Claim Transaction: { swap . claimTxHash }
1398+ </ p >
1399+ ) }
12561400
1257- { /* Show BTC claim transaction if it exists */ }
1258- { swap . btcClaimTxId && (
1259- < p className = "text-sm text-green-700" >
1260- BTC Claim Transaction: { swap . btcClaimTxId }
1261- </ p >
1262- ) }
1401+ { /* Show BTC claim transaction if it exists */ }
1402+ { swap . btcClaimTxId && (
1403+ < p className = "text-sm text-green-700" >
1404+ BTC Claim Transaction: { swap . btcClaimTxId }
1405+ </ p >
1406+ ) }
12631407
1264- { swap . secret ? (
1265- < div >
1266- < div className = "bg-white p-2 rounded border mb-3" >
1267- < p className = "text-xs font-medium text-gray-700" > Revealed Secret:</ p >
1268- < p className = "text-xs font-mono text-gray-600 break-all" > { swap . secret } </ p >
1408+ { swap . secret ? (
1409+ < div >
1410+ < div className = "bg-yellow-50 p-3 rounded border mb-3" >
1411+ < p className = "text-sm font-semibold text-gray-800 mb-2" > 🔑 Revealed Secret:</ p >
1412+ < p className = "text-sm font-mono text-gray-900 break-all bg-white p-2 rounded border border-yellow-200" > { swap . secret } </ p >
1413+ </ div >
1414+
1415+ { isUserTaker && swap . status === 'completed' && (
1416+ < div className = "space-y-2" >
1417+ < p className = "text-blue-800 text-sm font-medium" >
1418+ 👉 Now you can claim the creator's HTLC using this secret:
1419+ </ p >
1420+ < button
1421+ onClick = { claimWithExtractedSecret }
1422+ disabled = { claimingHtlc }
1423+ className = "bg-purple-600 text-white px-4 py-2 rounded-md hover:bg-purple-700 disabled:bg-gray-400 font-medium"
1424+ >
1425+ { claimingHtlc ? 'Claiming Creator HTLC...' : '🎯 Claim Creator HTLC' }
1426+ </ button >
12691427 </ div >
1428+ ) }
12701429
1271- { isUserTaker && (
1272- < div className = "space-y-2" >
1273- < p className = "text-blue-800 text-sm" >
1274- Now you can claim the creator's HTLC using this secret:
1275- </ p >
1276- < button
1277- onClick = { claimWithExtractedSecret }
1278- disabled = { claimingHtlc }
1279- className = "bg-purple-600 text-white px-4 py-2 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
1280- >
1281- { claimingHtlc ? 'Claiming Creator HTLC...' : 'Claim Creator HTLC' }
1282- </ button >
1283- </ div >
1284- ) }
1430+ { isUserCreator && swap . status === 'completed' && (
1431+ < p className = "text-green-700 text-sm" >
1432+ ⏳ Waiting for taker to claim your HTLC using the revealed secret...
1433+ </ p >
1434+ ) }
12851435
1286- { isUserCreator && (
1287- < p className = "text-green-700 text-sm" >
1288- ✅ Atomic swap completed! Both parties have their tokens.
1289- </ p >
1290- ) }
1291- </ div >
1292- ) : (
1293- < div >
1294- < p className = "text-blue-800 text-sm mb-2" >
1295- { isUserTaker ? "Extract the secret to claim the creator's HTLC:" : "The taker needs to extract the secret:" }
1436+ { swap . status === 'fully_completed' && (
1437+ < p className = "text-green-700 text-sm font-medium" >
1438+ ✅ Atomic swap completed! Both parties have their tokens.
12961439 </ p >
1297- < button
1298- onClick = { extractSecretFromClaim }
1299- className = "bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700"
1300- >
1301- Extract Secret from Claim
1302- </ button >
1303- </ div >
1304- ) }
1305- </ div >
1306- ) }
1440+ ) }
1441+ </ div >
1442+ ) : (
1443+ < div >
1444+ < p className = "text-orange-800 text-sm mb-2 font-medium" >
1445+ ⚠️ Secret not yet extracted. Click below to extract it from the claim transaction:
1446+ </ p >
1447+ < button
1448+ onClick = { extractSecretFromClaim }
1449+ className = "bg-blue-600 text-white px-4 py-2 rounded text-sm hover:bg-blue-700 font-medium"
1450+ >
1451+ 🔍 Extract Secret from Claim
1452+ </ button >
1453+ </ div >
1454+ ) }
1455+ </ div >
13071456 </ div >
13081457 ) }
13091458
0 commit comments