diff --git a/examples/swap-board-ml-btc/src/app/create/page.tsx b/examples/swap-board-ml-btc/src/app/create/page.tsx index b8698bf..af2b249 100644 --- a/examples/swap-board-ml-btc/src/app/create/page.tsx +++ b/examples/swap-board-ml-btc/src/app/create/page.tsx @@ -68,7 +68,7 @@ export default function CreateOfferPage() { try { if (client) { const connect = await client.connect() - const address = connect.testnet.receiving[0] + const address = connect.address.testnet.receiving[0] setUserAddress(address) } } catch (error) { diff --git a/examples/swap-board-ml-btc/src/app/offers/page.tsx b/examples/swap-board-ml-btc/src/app/offers/page.tsx index 273ee51..1fb9508 100644 --- a/examples/swap-board-ml-btc/src/app/offers/page.tsx +++ b/examples/swap-board-ml-btc/src/app/offers/page.tsx @@ -72,7 +72,7 @@ export default function OffersPage() { try { if (client) { const connect = await client.connect() - const address = connect.testnet.receiving[0] + const address = connect.address.testnet.receiving[0] setUserAddress(address) } } catch (error) { diff --git a/examples/swap-board-ml-btc/src/app/swap/[id]/page.tsx b/examples/swap-board-ml-btc/src/app/swap/[id]/page.tsx index 7b670c0..2a31f22 100644 --- a/examples/swap-board-ml-btc/src/app/swap/[id]/page.tsx +++ b/examples/swap-board-ml-btc/src/app/swap/[id]/page.tsx @@ -23,8 +23,6 @@ export default function SwapPage({ params }: { params: { id: string } }) { const [loading, setLoading] = useState(true) const [userAddress, setUserAddress] = useState('') const [client, setClient] = useState(null) - const [secretHash, setSecretHash] = useState(null) - const [generatingSecret, setGeneratingSecret] = useState(false) const [creatingHtlc, setCreatingHtlc] = useState(false) const [creatingCounterpartyHtlc, setCreatingCounterpartyHtlc] = useState(false) const [claimingHtlc, setClaimingHtlc] = useState(false) @@ -36,13 +34,27 @@ export default function SwapPage({ params }: { params: { id: string } }) { const [claimingBTCHtlc, setClaimingBTCHtlc] = useState(false) const [refundingBTCHtlc, setRefundingBTCHtlc] = useState(false) + // Refund availability state + const [refundInfo, setRefundInfo] = useState<{ + canRefund: boolean + blocksRemaining: number + timeRemaining: string + htlcTxId: string + confirmations?: number + timelockBlocks?: number + } | null>(null) + const [checkingRefund, setCheckingRefund] = useState(false) + useEffect(() => { fetchSwap() initializeClient() fetchTokens() // Poll for updates every 10 seconds - const interval = setInterval(fetchSwap, 10000) + const interval = setInterval(() => { + fetchSwap() + checkHTLCSpendStatus() + }, 10000) return () => clearInterval(interval) }, [params.id]) @@ -99,11 +111,145 @@ export default function SwapPage({ params }: { params: { id: string } }) { } } + const checkHTLCSpendStatus = async () => { + if (!swap || !client) return + + try { + const network = (process.env.NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet') || 'testnet' + const apiServer = network === 'mainnet' + ? 'https://api-server.mintlayer.org/api/v2' + : 'https://api-server-lovelace.mintlayer.org/api/v2' + + let needsUpdate = false + const updates: any = {} + let creatorHtlcSpent = false + let takerHtlcSpent = false + let creatorHtlcSpendTxId: string | null = null + let takerHtlcSpendTxId: string | null = null + + // Check creator's ML HTLC + if (swap.creatorHtlcTxHash) { + try { + const outputResponse = await fetch(`${apiServer}/transaction/${swap.creatorHtlcTxHash}/output/0`) + if (outputResponse.ok) { + const outputData = await outputResponse.json() + creatorHtlcSpent = outputData.spent === true + if (creatorHtlcSpent && outputData.spent_by) { + creatorHtlcSpendTxId = outputData.spent_by + console.log('Creator HTLC has been spent by:', creatorHtlcSpendTxId) + } + } + } catch (error) { + console.error('Error checking creator HTLC spend status:', error) + } + } + + // Check taker's ML HTLC + if (swap.takerHtlcTxHash) { + try { + const outputResponse = await fetch(`${apiServer}/transaction/${swap.takerHtlcTxHash}/output/0`) + if (outputResponse.ok) { + const outputData = await outputResponse.json() + takerHtlcSpent = outputData.spent === true + if (takerHtlcSpent && outputData.spent_by) { + takerHtlcSpendTxId = outputData.spent_by + console.log('Taker HTLC has been spent by:', takerHtlcSpendTxId) + } + } + } catch (error) { + console.error('Error checking taker HTLC spend status:', error) + } + } + + // Determine status based on what's been spent + if (creatorHtlcSpent && takerHtlcSpent) { + // Both HTLCs spent = fully completed + if (swap.status !== 'fully_completed') { + needsUpdate = true + updates.status = 'fully_completed' + } + } else if (creatorHtlcSpent || takerHtlcSpent) { + // One HTLC spent = first claim completed, need to extract secret + if (swap.status === 'fully_completed') { + // Downgrade from fully_completed to completed if only one is actually spent + needsUpdate = true + updates.status = 'completed' + console.log('Downgrading status from fully_completed to completed - only one HTLC is spent') + } else if (swap.status !== 'completed') { + needsUpdate = true + updates.status = 'completed' + } + + // Try to extract the secret from the claim transaction if we don't have it + if (!swap.secret) { + try { + const spendTxId = creatorHtlcSpent ? creatorHtlcSpendTxId : takerHtlcSpendTxId + const htlcTxId = creatorHtlcSpent ? swap.creatorHtlcTxHash : swap.takerHtlcTxHash + + if (spendTxId && htlcTxId) { + console.log('Attempting to extract secret from claim transaction:', spendTxId) + + // Fetch the claim transaction to get its hex + const claimTxResponse = await fetch(`${apiServer}/transaction/${spendTxId}`) + if (claimTxResponse.ok) { + const claimTxData = await claimTxResponse.json() + const claimTxHex = claimTxData.hex + + if (claimTxHex) { + // Extract the secret using the SDK + const extractedSecret = await client.extractHtlcSecret({ + transaction_id: spendTxId, + transaction_hex: claimTxHex, + format: 'hex' + }) + + console.log('Successfully extracted secret:', extractedSecret) + updates.secret = extractedSecret + updates.claimTxHash = spendTxId + updates.claimTxHex = claimTxHex + needsUpdate = true + } + } + } + } catch (error) { + console.error('Error extracting secret from claim transaction:', error) + // Continue with status update even if secret extraction fails + } + } + } else { + // Neither HTLC is spent - downgrade status if needed + if (swap.status === 'completed' || swap.status === 'fully_completed') { + needsUpdate = true + // Determine appropriate status based on what HTLCs exist + if (swap.creatorHtlcTxHash && swap.takerHtlcTxHash) { + updates.status = 'in_progress' + } else if (swap.creatorHtlcTxHash || swap.takerHtlcTxHash) { + updates.status = 'htlc_created' + } + console.log('Downgrading status - no HTLCs are actually spent on-chain') + } + } + + // Update swap if needed + if (needsUpdate) { + console.log('Updating swap status based on spend status:', updates) + await fetch(`/api/swaps/${swap.id}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updates) + }) + fetchSwap() + } + } catch (error) { + console.error('Error checking HTLC spend status:', error) + } + } + const connectWallet = async () => { try { if (client) { const connect = await client.connect() - const address = connect.testnet.receiving[0] + const address = connect.address.testnet.receiving[0] setUserAddress(address) // If swap involves BTC, also get BTC address @@ -123,46 +269,8 @@ export default function SwapPage({ params }: { params: { id: string } }) { } } - const generateSecretHash = async () => { - if (!client || !swap) { - alert('Please connect your wallet first') - return - } - - setGeneratingSecret(true) - try { - const secretHashResponse = await client.requestSecretHash({}) - setSecretHash(secretHashResponse) - console.log('Generated secret hash:', secretHashResponse) - - // Save the secret hash to the database - const response = await fetch(`/api/swaps/${swap.id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - secretHash: JSON.stringify(secretHashResponse) - }), - }) - - if (!response.ok) { - throw new Error('Failed to save secret hash') - } - - // Refresh swap data to show the updated secret hash - fetchSwap() - - } catch (error) { - console.error('Error generating secret hash:', error) - alert('Failed to generate secret hash. Please try again.') - } finally { - setGeneratingSecret(false) - } - } - const createHtlc = async () => { - if (!client || !userAddress || !secretHash || !swap?.offer) { + if (!client || !userAddress || !swap?.offer) { alert('Missing required data for HTLC creation') return } @@ -170,10 +278,11 @@ export default function SwapPage({ params }: { params: { id: string } }) { setCreatingHtlc(true) try { // Step 1: Build the HTLC transaction + // Note: We pass a placeholder secret hash, and the wallet generates the actual secret hash internally const htlcParams = { amount: swap.offer.amountA, token_id: swap.offer.tokenA === 'ML' ? null : swap.offer.tokenA, - secret_hash: { hex: secretHash.secret_hash_hex }, + secret_hash: { hex: '0000000000000000000000000000000000000000' }, // Placeholder - wallet generates actual secret spend_address: swap.takerMLAddress, // Taker can spend with secret refund_address: userAddress, // Creator can refund after timelock refund_timelock: { @@ -182,7 +291,7 @@ export default function SwapPage({ params }: { params: { id: string } }) { } } - // Step 2: Sign the transaction + // Step 2: Sign the transaction (wallet generates secret hash internally) const signedTxHex = await client.createHtlc(htlcParams) console.log('HTLC signed:', signedTxHex) @@ -200,7 +309,6 @@ export default function SwapPage({ params }: { params: { id: string } }) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'htlc_created', - secretHash: JSON.stringify(secretHash), creatorHtlcTxHash: txId, creatorHtlcTxHex: signedTxHex }) @@ -218,20 +326,46 @@ export default function SwapPage({ params }: { params: { id: string } }) { } const createCounterpartyHtlc = async () => { - if (!client || !userAddress || !swap?.offer || !swap.secretHash) { + if (!client || !userAddress || !swap?.offer) { alert('Missing required data for counterparty HTLC creation') return } + // Check if creator's HTLC exists to extract secret hash from + if (!swap.creatorHtlcTxHash) { + alert('Creator HTLC must be created first') + return + } + setCreatingCounterpartyHtlc(true) try { - // Parse the stored secret hash from the creator's HTLC - const creatorSecretHash = JSON.parse(swap.secretHash) + // Fetch the creator's ML HTLC transaction to extract the secret hash + const network = (process.env.NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet') || 'testnet' + const apiServer = network === 'mainnet' + ? 'https://api-server.mintlayer.org/api/v2' + : 'https://api-server-lovelace.mintlayer.org/api/v2' + + const txResponse = await fetch(`${apiServer}/transaction/${swap.creatorHtlcTxHash}`) + if (!txResponse.ok) { + throw new Error('Failed to fetch creator HTLC transaction') + } + + const txData = await txResponse.json() + console.log('Creator HTLC transaction data:', txData) + + // Find the HTLC output and extract the secret hash + const htlcOutput = txData.outputs?.find((output: any) => output.type === 'Htlc') + if (!htlcOutput || !htlcOutput.htlc?.secret_hash?.hex) { + throw new Error('Could not find secret hash in creator HTLC transaction') + } + + const secretHashHex = htlcOutput.htlc.secret_hash.hex + console.log('Extracted secret hash from creator HTLC:', secretHashHex) const htlcParams = { amount: swap.offer.amountB, // Taker gives amountB token_id: swap.offer.tokenB === 'ML' ? null : swap.offer.tokenB, - secret_hash: { hex: creatorSecretHash.secret_hash_hex }, // Use same secret hash + secret_hash: { hex: secretHashHex }, // Use same secret hash from creator's HTLC spend_address: swap.offer.creatorMLAddress, // Creator can spend with secret refund_address: userAddress, // Taker can refund after timelock refund_timelock: { @@ -524,7 +658,7 @@ export default function SwapPage({ params }: { params: { id: string } }) { // BTC HTLC Functions const createBTCHTLC = async () => { - if (!client || !userAddress || !swap?.offer || !swap.secretHash) { + if (!client || !userAddress || !swap?.offer) { alert('Missing required data for BTC HTLC creation') return } @@ -534,19 +668,49 @@ export default function SwapPage({ params }: { params: { id: string } }) { return } + // Check if ML HTLC exists to extract secret hash from + const mlHtlcTxHash = swap.creatorHtlcTxHash || swap.takerHtlcTxHash + if (!mlHtlcTxHash) { + alert('ML HTLC must be created first to generate the secret hash') + return + } + setCreatingBTCHtlc(true) try { validateSwapForBTCHTLC(swap, swap.offer) + // Fetch the ML HTLC transaction to extract the secret hash + const network = (process.env.NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet') || 'testnet' + const apiServer = network === 'mainnet' + ? 'https://api-server.mintlayer.org/api/v2' + : 'https://api-server-lovelace.mintlayer.org/api/v2' + + const txResponse = await fetch(`${apiServer}/transaction/${mlHtlcTxHash}`) + if (!txResponse.ok) { + throw new Error('Failed to fetch ML HTLC transaction') + } + + const txData = await txResponse.json() + console.log('ML HTLC transaction data:', txData) + + // Find the HTLC output and extract the secret hash + const htlcOutput = txData.outputs?.find((output: any) => output.type === 'Htlc') + if (!htlcOutput || !htlcOutput.htlc?.secret_hash?.hex) { + throw new Error('Could not find secret hash in ML HTLC transaction') + } + + const secretHashHex = htlcOutput.htlc.secret_hash.hex + console.log('Extracted secret hash from ML HTLC:', secretHashHex) + const isUserCreator = swap.offer.creatorMLAddress === userAddress let request; if (isUserCreator && isCreatorOfferingBTC(swap.offer)) { // Creator is offering BTC - request = buildCreatorBTCHTLCRequest(swap, swap.offer, swap.secretHash) + request = buildCreatorBTCHTLCRequest(swap, swap.offer, secretHashHex) } else if (!isUserCreator && !isCreatorOfferingBTC(swap.offer)) { // Taker is offering BTC (creator wants BTC) - request = buildTakerBTCHTLCRequest(swap, swap.offer, swap.secretHash) + request = buildTakerBTCHTLCRequest(swap, swap.offer, secretHashHex) } else { alert('You are not the one who should create the BTC HTLC') return @@ -556,7 +720,6 @@ export default function SwapPage({ params }: { params: { id: string } }) { const response = await (client as any).request({ method: 'signTransaction', params: { chain: 'bitcoin', txData: { JSONRepresentation: request } } }) // Broadcast the transaction using Blockstream API - const network = (process.env.NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet') || 'testnet' const txId = await broadcastBTCTransaction(response.signedTxHex, network === 'testnet') // Update swap with BTC HTLC details @@ -649,6 +812,146 @@ export default function SwapPage({ params }: { params: { id: string } }) { } } + const checkRefundAvailability = async () => { + if (!swap || !userAddress) { + alert('Missing required data') + return + } + + setCheckingRefund(true) + try { + const network = (process.env.NEXT_PUBLIC_MINTLAYER_NETWORK as 'testnet' | 'mainnet') || 'testnet' + const apiServer = network === 'mainnet' + ? 'https://api-server.mintlayer.org/api/v2' + : 'https://api-server-lovelace.mintlayer.org/api/v2' + + // Determine which HTLC the user can refund + const isUserCreator = swap.offer?.creatorMLAddress === userAddress + const isUserTaker = swap.takerMLAddress === userAddress + + let htlcTxId: string | null = null + + if (isUserCreator && swap.creatorHtlcTxHash) { + htlcTxId = swap.creatorHtlcTxHash + } else if (isUserTaker && swap.takerHtlcTxHash) { + htlcTxId = swap.takerHtlcTxHash + } + + if (!htlcTxId) { + alert('No HTLC found for refund') + return + } + + // Fetch the HTLC transaction + const txResponse = await fetch(`${apiServer}/transaction/${htlcTxId}`) + if (!txResponse.ok) { + throw new Error('Failed to fetch HTLC transaction') + } + + const txData = await txResponse.json() + console.log('HTLC transaction data:', txData) + + // Find the HTLC output and extract the timelock + const htlcOutput = txData.outputs?.find((output: any) => output.type === 'Htlc') + if (!htlcOutput || !htlcOutput.htlc?.refund_timelock) { + throw new Error('Could not find timelock in HTLC transaction') + } + + const refundTimelock = htlcOutput.htlc.refund_timelock + console.log('Refund timelock:', refundTimelock) + + // Get the confirmations count + const confirmations = txData.confirmations || 0 + console.log('Confirmations:', confirmations) + + // Compare confirmations with timelock content + let timelockBlocks: number + let canRefund: boolean + let blocksRemaining: number + + if (refundTimelock.type === 'ForBlockCount') { + timelockBlocks = refundTimelock.content + canRefund = confirmations >= timelockBlocks + blocksRemaining = Math.max(0, timelockBlocks - confirmations) + } else { + // For UntilTime, we'd need different logic + alert('Time-based timelocks are not yet supported in this UI') + return + } + + console.log('Timelock blocks:', timelockBlocks) + console.log('Can refund:', canRefund) + console.log('Blocks remaining:', blocksRemaining) + + // Calculate approximate time remaining (2 minutes per block) + const minutesRemaining = blocksRemaining * 2 + let timeRemaining: string + + if (canRefund) { + timeRemaining = 'Available now' + } else if (minutesRemaining < 60) { + timeRemaining = `~${minutesRemaining} minutes` + } else { + const hoursRemaining = Math.floor(minutesRemaining / 60) + const remainingMinutes = minutesRemaining % 60 + timeRemaining = `~${hoursRemaining}h ${remainingMinutes}m` + } + + setRefundInfo({ + canRefund, + blocksRemaining, + timeRemaining, + htlcTxId, + confirmations, + timelockBlocks + }) + + console.log('Refund info:', { canRefund, blocksRemaining, timeRemaining, htlcTxId, confirmations, timelockBlocks }) + } catch (error) { + console.error('Error checking refund availability:', error) + alert('Failed to check refund availability. Please try again.') + } finally { + setCheckingRefund(false) + } + } + + const refundMLHTLC = async () => { + if (!client || !refundInfo || !refundInfo.canRefund) { + alert('Refund is not yet available') + return + } + + try { + // Refund the HTLC + const signedTxHex = await client.refundHtlc({ + transaction_id: refundInfo.htlcTxId + }) + console.log('Refund transaction signed:', signedTxHex) + + // Broadcast the refund transaction + const broadcastResult = await client.broadcastTx(signedTxHex) + console.log('Refund broadcast result:', broadcastResult) + + const txId = broadcastResult.tx_id || broadcastResult.transaction_id || broadcastResult.id + + // Update swap status + await fetch(`/api/swaps/${swap!.id}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + status: 'refunded' + }) + }) + + fetchSwap() + alert(`HTLC refunded successfully! TX ID: ${txId}`) + setRefundInfo(null) + } catch (error) { + console.error('Error refunding HTLC:', error) + alert('Failed to refund HTLC. Please try again.') + } + } + const refundBTCHTLC = async () => { if (!client || !userAddress || !swap?.offer) { alert('Missing required data for BTC HTLC refund') @@ -723,7 +1026,7 @@ export default function SwapPage({ params }: { params: { id: string } }) { case 'btc_htlc_created': return 'BTC HTLC created, waiting for ML HTLC' case 'both_htlcs_created': return 'Both HTLCs created, ready to claim' case 'in_progress': return 'Claims in progress' - case 'completed': return 'First claim completed, waiting for final claim' + case 'completed': return 'First claim completed (secret revealed), waiting for final claim' case 'fully_completed': return 'Atomic swap completed successfully' case 'refunded': return 'ML HTLC refunded due to timeout' case 'btc_refunded': return 'BTC HTLC refunded due to timeout' @@ -769,7 +1072,7 @@ export default function SwapPage({ params }: { params: { id: string } }) { )} -
+
{swap.status.toUpperCase()} @@ -779,6 +1082,15 @@ export default function SwapPage({ params }: { params: { id: string } }) { Connected: {userAddress.slice(0, 10)}... )} + {(swap.status === 'in_progress' || swap.status === 'completed' || swap.status === 'both_htlcs_created') && ( + + )}
@@ -902,7 +1214,7 @@ export default function SwapPage({ params }: { params: { id: string } }) {
First claim completed (secret revealed) @@ -912,13 +1224,6 @@ export default function SwapPage({ params }: { params: { id: string } }) {
-
-
- Secret extracted -
-

{isUserCreator - ? "You need to create the initial HTLC to start the swap process." + ? "You need to create the initial HTLC to start the swap process. The secret will be generated securely within your wallet." : "Waiting for the creator to create the initial HTLC." }

{isUserCreator && (
- {!secretHash ? ( -
-

- Step 1: Generate a secret hash for the HTLC -

+ {/* Show appropriate HTLC creation button based on what creator is offering */} + {swap.offer && offerInvolvesBTC(swap.offer) ? ( + // BTC is involved - creator creates the HTLC for what they're offering + isCreatorOfferingBTC(swap.offer) ? ( + // Creator offers BTC -> create BTC HTLC first -
+ ) : ( + // Creator offers ML -> create ML HTLC first + + ) ) : ( -
-

- ✅ Secret hash generated successfully -

-
- {JSON.stringify(secretHash, null, 2)} -
-

- Step 2: Create the HTLC contract -

- - {/* Show appropriate HTLC creation button based on what creator is offering */} - {swap.offer && offerInvolvesBTC(swap.offer) ? ( - // BTC is involved - creator creates the HTLC for what they're offering - isCreatorOfferingBTC(swap.offer) ? ( - // Creator offers BTC -> create BTC HTLC first - - ) : ( - // Creator offers ML -> create ML HTLC first - - ) - ) : ( - // No BTC involved - standard ML HTLC - - )} -
+ // No BTC involved - standard ML HTLC + )}
)} @@ -1024,7 +1302,6 @@ export default function SwapPage({ params }: { params: { id: string } }) {

Creator's HTLC Details:

Amount: {swap.offer?.amountA} {getTokenSymbol(swap.offer?.tokenA || '')}
-
Secret Hash: {swap.secretHash ? JSON.parse(swap.secretHash).secret_hash_hex.slice(0, 20) + '...' : 'N/A'}
{swap.creatorHtlcTxHash && (
ML TX ID: {swap.creatorHtlcTxHash.slice(0, 20)}...
)} @@ -1098,74 +1375,84 @@ export default function SwapPage({ params }: { params: { id: string } }) { {(swap.status === 'completed' || swap.status === 'fully_completed') && (
- {(swap.claimTxHash || swap.btcClaimTxId) && ( -
-

- {swap.status === 'fully_completed' - ? "🎉 Atomic swap completed successfully! Both parties have their tokens." - : isUserCreator - ? "You have claimed the taker's HTLC!" - : "The creator has claimed your HTLC!" +

+

+ {swap.status === 'fully_completed' + ? "🎉 Atomic swap completed successfully! Both parties have their tokens." + : "✅ First HTLC claimed! Secret has been revealed." + } +

+ {swap.status === 'completed' && ( +

+ {isUserCreator + ? "You claimed the taker's HTLC. The taker can now use the revealed secret to claim your HTLC." + : "The creator claimed your HTLC and revealed the secret. You can now claim the creator's HTLC!" }

+ )} - {/* Show ML claim transaction if it exists */} - {swap.claimTxHash && ( -

- ML Claim Transaction: {swap.claimTxHash} -

- )} + {/* Show ML claim transaction if it exists */} + {swap.claimTxHash && ( +

+ ML Claim Transaction: {swap.claimTxHash} +

+ )} - {/* Show BTC claim transaction if it exists */} - {swap.btcClaimTxId && ( -

- BTC Claim Transaction: {swap.btcClaimTxId} -

- )} + {/* Show BTC claim transaction if it exists */} + {swap.btcClaimTxId && ( +

+ BTC Claim Transaction: {swap.btcClaimTxId} +

+ )} + + {swap.secret ? ( +
+
+

🔑 Revealed Secret:

+

{swap.secret}

+
- {swap.secret ? ( -
-
-

Revealed Secret:

-

{swap.secret}

+ {isUserTaker && swap.status === 'completed' && ( +
+

+ 👉 Now you can claim the creator's HTLC using this secret: +

+
+ )} - {isUserTaker && ( -
-

- Now you can claim the creator's HTLC using this secret: -

- -
- )} + {isUserCreator && swap.status === 'completed' && ( +

+ ⏳ Waiting for taker to claim your HTLC using the revealed secret... +

+ )} - {isUserCreator && ( -

- ✅ Atomic swap completed! Both parties have their tokens. -

- )} -
- ) : ( -
-

- {isUserTaker ? "Extract the secret to claim the creator's HTLC:" : "The taker needs to extract the secret:"} + {swap.status === 'fully_completed' && ( +

+ ✅ Atomic swap completed! Both parties have their tokens.

- -
- )} -
- )} + )} +
+ ) : ( +
+

+ ⚠️ Secret not yet extracted. Click below to extract it from the claim transaction: +

+ +
+ )} +
)} @@ -1297,7 +1584,7 @@ export default function SwapPage({ params }: { params: { id: string } }) { {!isCreatorOfferingBTC(swap.offer) && !isUserCreator && swap.creatorHtlcTxHash && ( + + {!refundInfo ? ( + + ) : ( +
+
+
+
+ Status: + + {refundInfo.canRefund ? '✓ Refund Available' : '⏳ Timelock Active'} + +
+
+ Confirmations: + {refundInfo.confirmations || 0} / {refundInfo.timelockBlocks || 0} +
+ {!refundInfo.canRefund && ( + <> +
+ Blocks Remaining: + {refundInfo.blocksRemaining} +
+
+ Time Remaining: + {refundInfo.timeRemaining} +
+ + )} +
+ HTLC TX: + {refundInfo.htlcTxId.slice(0, 16)}... +
+
+
+ +
+ + +
+
+ )}
)}