Skip to content

Commit

Permalink
Merge pull request #772 from BoltzExchange/rsk-server-claim
Browse files Browse the repository at this point in the history
feat: help server claim Chain Swaps that receive on EVM
  • Loading branch information
michael1011 authored Dec 15, 2024
2 parents 835a841 + 164f535 commit 54612c2
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 69 deletions.
45 changes: 42 additions & 3 deletions src/components/SwapChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand All @@ -35,6 +40,8 @@ type SwapStatus = {
transaction?: SwapStatusTransaction;
};

const coopClaimableSymbols = [BTC, LBTC];

const reconnectInterval = 5_000;

class BoltzWebSocket {
Expand Down Expand Up @@ -124,7 +131,9 @@ class BoltzWebSocket {
return;
}

log.debug("WebSocket message", data);
log.debug(
`WebSocket message: ${JSON.stringify(data, null, 2)}`,
);

if (data.event === "update" && data.channel === "swap.update") {
const swapUpdates = data.args as SwapStatus[];
Expand Down Expand Up @@ -196,6 +205,15 @@ export const SwapChecker = () => {
return;
}

if (
currentSwap.type === SwapType.Chain &&
data.status === swapStatusPending.TransactionClaimPending &&
coopClaimableSymbols.includes((currentSwap as ChainSwap).assetSend)
) {
await helpServerClaim(currentSwap as ChainSwap);
return;
}

if (getRelevantAssetForSwap(currentSwap) === RBTC) {
if (
data.status === swapStatusPending.TransactionMempool &&
Expand Down Expand Up @@ -285,6 +303,27 @@ export const SwapChecker = () => {
}
};

const helpServerClaim = async (swap: ChainSwap) => {
if (swap.claimTx === undefined) {
log.warn(
`Not helping server claim Chain Swap ${swap.id} because we have not claimed yet`,
);
return;
}

try {
log.debug(
`Helping server claim ${swap.assetSend} of Chain Swap ${swap.id}`,
);
const sig = await createTheirPartialChainSwapSignature(swap);
await postChainSwapDetails(swap.id, undefined, sig);
} catch (e) {
log.warn(
`Helping server claim Chain Swap ${swap.id} failed: ${formatError(e)}`,
);
}
};

onMount(async () => {
const swapsToCheck = (await getSwaps()).filter(
(s) =>
Expand Down
4 changes: 2 additions & 2 deletions src/utils/boltzClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand Down
127 changes: 63 additions & 64 deletions src/utils/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,68 @@ const claimReverseSwap = async (
}
};

export const createTheirPartialChainSwapSignature = async (
swap: ChainSwap,
): Promise<Awaited<ReturnType<typeof postChainSwapDetails>> | 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,
Expand Down Expand Up @@ -191,75 +253,12 @@ const claimChainSwap = async (
return claimTx;
}

const createTheirPartialSignature = async (): Promise<
Awaited<ReturnType<typeof postChainSwapDetails>> | 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(),
Expand Down

0 comments on commit 54612c2

Please sign in to comment.