From 1e6bbc53802cc26e210edd40a07e692e7cc890c6 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Thu, 12 Dec 2024 13:44:31 +0100 Subject: [PATCH] feat: help server claim Chain Swaps that receive on EVM --- src/components/SwapChecker.tsx | 28 +++++++- src/utils/boltzClient.ts | 4 +- src/utils/claim.ts | 127 ++++++++++++++++----------------- 3 files changed, 91 insertions(+), 68 deletions(-) diff --git a/src/components/SwapChecker.tsx b/src/components/SwapChecker.tsx index b906a030..bca0d2b0 100644 --- a/src/components/SwapChecker.tsx +++ b/src/components/SwapChecker.tsx @@ -3,7 +3,7 @@ import log from "loglevel"; import { createEffect, onCleanup, onMount } from "solid-js"; import { config } from "../config"; -import { RBTC } from "../consts/Assets"; +import { BTC, LBTC, RBTC } from "../consts/Assets"; import { SwapType } from "../consts/Enums"; import { swapStatusFinal, @@ -15,8 +15,13 @@ import { SwapStatusTransaction, usePayContext } from "../context/Pay"; import { getChainSwapTransactions, getReverseTransaction, + postChainSwapDetails, } from "../utils/boltzClient"; -import { claim, createSubmarineSignature } from "../utils/claim"; +import { + claim, + createSubmarineSignature, + createTheirPartialChainSwapSignature, +} from "../utils/claim"; import { formatError } from "../utils/errors"; import { getApiUrl } from "../utils/helper"; import Lock from "../utils/lock"; @@ -35,6 +40,8 @@ type SwapStatus = { transaction?: SwapStatusTransaction; }; +const coopClaimableSymbols = [BTC, LBTC]; + const reconnectInterval = 5_000; class BoltzWebSocket { @@ -196,6 +203,15 @@ export const SwapChecker = () => { return; } + if ( + currentSwap.type === SwapType.Chain && + data.status === "transaction.claim.pending" && + coopClaimableSymbols.includes((currentSwap as ChainSwap).assetSend) + ) { + await helpServerClaim(currentSwap as ChainSwap); + return; + } + if (getRelevantAssetForSwap(currentSwap) === RBTC) { if ( data.status === swapStatusPending.TransactionMempool && @@ -285,6 +301,14 @@ export const SwapChecker = () => { } }; + const helpServerClaim = async (swap: ChainSwap) => { + log.debug( + `Helping server claiming ${swap.assetSend} of Chain Swap ${swap.id}`, + ); + const sig = await createTheirPartialChainSwapSignature(swap); + await postChainSwapDetails(swap.id, undefined, sig); + }; + onMount(async () => { const swapsToCheck = (await getSwaps()).filter( (s) => diff --git a/src/utils/boltzClient.ts b/src/utils/boltzClient.ts index b487b14b..95747920 100644 --- a/src/utils/boltzClient.ts +++ b/src/utils/boltzClient.ts @@ -416,9 +416,9 @@ export const getChainSwapClaimDetails = (id: string) => export const postChainSwapDetails = ( id: string, - preimage: string, + preimage: string | undefined, signature: { pubNonce: string; partialSignature: string }, - toSign: { pubNonce: string; transaction: string; index: number }, + toSign?: { pubNonce: string; transaction: string; index: number }, ) => { checkCooperative(); return fetcher<{ diff --git a/src/utils/claim.ts b/src/utils/claim.ts index 73a433ea..c9fdd4ab 100644 --- a/src/utils/claim.ts +++ b/src/utils/claim.ts @@ -137,6 +137,68 @@ const claimReverseSwap = async ( } }; +export const createTheirPartialChainSwapSignature = async ( + swap: ChainSwap, +): Promise> | undefined> => { + // RSK claim transactions can't be signed cooperatively + if (swap.assetSend === RBTC) { + return undefined; + } + + // Sign the claim transaction of the server + try { + const serverClaimDetails = await getChainSwapClaimDetails(swap.id); + + const boltzClaimPublicKey = Buffer.from( + serverClaimDetails.publicKey, + "hex", + ); + const theirClaimMusig = await createMusig( + parsePrivateKey(swap.refundPrivateKey), + boltzClaimPublicKey, + ); + tweakMusig( + swap.assetSend, + theirClaimMusig, + SwapTreeSerializer.deserializeSwapTree(swap.lockupDetails.swapTree) + .tree, + ); + theirClaimMusig.aggregateNonces([ + [ + boltzClaimPublicKey, + Buffer.from(serverClaimDetails.pubNonce, "hex"), + ], + ]); + theirClaimMusig.initializeSession( + Buffer.from(serverClaimDetails.transactionHash, "hex"), + ); + + return { + pubNonce: Buffer.from(theirClaimMusig.getPublicNonce()).toString( + "hex", + ), + partialSignature: Buffer.from( + theirClaimMusig.signPartial(), + ).toString("hex"), + }; + } catch (err) { + if (typeof err.json !== "function") { + throw err; + } + + const errMessage = (await err.json()).error; + if (errMessage !== "swap not eligible for a cooperative claim") { + throw err; + } + + log.debug( + `backend already broadcast their claim for chain swap ${swap.id}`, + ); + } + + return undefined; +}; + const claimChainSwap = async ( swap: ChainSwap, lockupTx: TransactionInterface, @@ -191,75 +253,12 @@ const claimChainSwap = async ( return claimTx; } - const createTheirPartialSignature = async (): Promise< - Awaited> | undefined - > => { - // RSK claim transactions can't be signed cooperatively - if (swap.assetSend === RBTC) { - return undefined; - } - - // Sign the claim transaction of the server - try { - const serverClaimDetails = await getChainSwapClaimDetails(swap.id); - - const boltzClaimPublicKey = Buffer.from( - serverClaimDetails.publicKey, - "hex", - ); - const theirClaimMusig = await createMusig( - parsePrivateKey(swap.refundPrivateKey), - boltzClaimPublicKey, - ); - tweakMusig( - swap.assetSend, - theirClaimMusig, - SwapTreeSerializer.deserializeSwapTree( - swap.lockupDetails.swapTree, - ).tree, - ); - theirClaimMusig.aggregateNonces([ - [ - boltzClaimPublicKey, - Buffer.from(serverClaimDetails.pubNonce, "hex"), - ], - ]); - theirClaimMusig.initializeSession( - Buffer.from(serverClaimDetails.transactionHash, "hex"), - ); - - return { - pubNonce: Buffer.from( - theirClaimMusig.getPublicNonce(), - ).toString("hex"), - partialSignature: Buffer.from( - theirClaimMusig.signPartial(), - ).toString("hex"), - }; - } catch (err) { - if (typeof err.json !== "function") { - throw err; - } - - const errMessage = (await err.json()).error; - if (errMessage !== "swap not eligible for a cooperative claim") { - throw err; - } - - log.debug( - `backend already broadcast their claim for chain swap ${swap.id}`, - ); - } - - return undefined; - }; - try { // Post our partial signature to ask for theirs const theirPartial = await postChainSwapDetails( swap.id, swap.preimage, - await createTheirPartialSignature(), + await createTheirPartialChainSwapSignature(swap), { index: 0, transaction: claimTx.toHex(),