Skip to content

Commit

Permalink
feat: add EVM address to swapinfo command
Browse files Browse the repository at this point in the history
When calling "swapinfo" with an EVM address, it will query all swaps
that have coins locked that can be claimed by that address.
  • Loading branch information
michael1011 committed Feb 28, 2025
1 parent b36d12b commit addbf7f
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 78 deletions.
11 changes: 9 additions & 2 deletions lib/db/repositories/ReverseRoutingHintRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@ class ReverseRoutingHintRepository {
},
});

public static getHints = (swapIds: string[]) =>
ReverseRoutingHint.findAll({
public static getHints = async (
swapIds: string[],
): Promise<ReverseRoutingHint[]> => {
if (swapIds.length === 0) {
return [];
}

return await ReverseRoutingHint.findAll({
where: {
swapId: {
[Op.in]: swapIds,
},
},
});
};
}

export default ReverseRoutingHintRepository;
103 changes: 75 additions & 28 deletions lib/notifications/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { Op } from 'sequelize';
import { satoshisToSatcomma } from '../DenominationConverter';
import Logger from '../Logger';
import Tracing from '../Tracing';
import { formatError, mapToObject, stringify } from '../Utils';
import { checkEvmAddress, formatError, mapToObject, stringify } from '../Utils';
import {
FinalChainSwapEvents,
FinalReverseSwapEvents,
FinalSwapEvents,
SwapType,
SwapUpdateEvent,
swapTypeToPrettyString,
swapTypeToString,
} from '../consts/Enums';
Expand All @@ -18,6 +19,7 @@ import Stats from '../data/Stats';
import ChannelCreation from '../db/models/ChannelCreation';
import ReverseRoutingHint from '../db/models/ReverseRoutingHint';
import ReverseSwap from '../db/models/ReverseSwap';
import Swap from '../db/models/Swap';
import ChainSwapRepository, {
ChainSwapInfo,
} from '../db/repositories/ChainSwapRepository';
Expand Down Expand Up @@ -303,41 +305,86 @@ class CommandHandler {

const identifier = args[0];

const [swaps, reverseSwaps, chainSwaps] = await Promise.all([
SwapRepository.getSwaps({
[Op.or]: {
id: identifier,
invoice: identifier,
preimageHash: identifier,
lockupAddress: identifier,
lockupTransactionId: identifier,
},
}),
ReverseSwapRepository.getReverseSwaps({
[Op.or]: {
id: identifier,
invoice: identifier,
preimageHash: identifier,
lockupAddress: identifier,
transactionId: identifier,
},
}),
ChainSwapRepository.getChainSwapsByData(
{
// When it's an EVM address, we query only funds locked for that address
try {
const evmAddress = checkEvmAddress(identifier);

const [reverseSwaps, chainSwaps] = await Promise.all([
ReverseSwapRepository.getReverseSwaps({
status: {
[Op.in]: [
SwapUpdateEvent.TransactionMempool,
SwapUpdateEvent.TransactionConfirmed,
],
},
claimAddress: evmAddress,
}),
ChainSwapRepository.getChainSwaps({
status: {
[Op.in]: [
SwapUpdateEvent.TransactionServerMempool,
SwapUpdateEvent.TransactionServerConfirmed,
],
},
}),
]);

this.notificationClient.sendMessage(`Funds locked for \`${evmAddress}\``);
await this.sendSwapInfos(
identifier,
[],
reverseSwaps,
chainSwaps.filter((s) => s.sendingData.claimAddress === evmAddress),
);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
// When it's not EVM address, we try to find the swap by some other identifier
const [swaps, reverseSwaps, chainSwaps] = await Promise.all([
SwapRepository.getSwaps({
[Op.or]: {
id: identifier,
invoice: identifier,
preimageHash: identifier,
lockupAddress: identifier,
transactionId: identifier,
lockupTransactionId: identifier,
},
},
{
}),
ReverseSwapRepository.getReverseSwaps({
[Op.or]: {
id: identifier,
invoice: identifier,
preimageHash: identifier,
lockupAddress: identifier,
transactionId: identifier,
},
},
),
]);
}),
ChainSwapRepository.getChainSwapsByData(
{
[Op.or]: {
lockupAddress: identifier,
transactionId: identifier,
},
},
{
[Op.or]: {
id: identifier,
preimageHash: identifier,
},
},
),
]);

await this.sendSwapInfos(identifier, swaps, reverseSwaps, chainSwaps);
}
};

private sendSwapInfos = async (
identifier: string,
swaps: Swap[],
reverseSwaps: ReverseSwap[],
chainSwaps: ChainSwapInfo[],
) => {
const reverseRoutingHints = new Map<string, ReverseRoutingHint>(
(
await ReverseRoutingHintRepository.getHints(
Expand Down
196 changes: 148 additions & 48 deletions test/unit/notifications/CommandHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { Op } from 'sequelize';
import { satoshisToSatcomma } from '../../../lib/DenominationConverter';
import Logger from '../../../lib/Logger';
import { getHexBuffer, mapToObject, stringify } from '../../../lib/Utils';
import { SwapType, swapTypeToString } from '../../../lib/consts/Enums';
import {
checkEvmAddress,
getHexBuffer,
mapToObject,
stringify,
} from '../../../lib/Utils';
import {
SwapType,
SwapUpdateEvent,
swapTypeToString,
} from '../../../lib/consts/Enums';
import ReferralStats from '../../../lib/data/ReferralStats';
import Stats from '../../../lib/data/Stats';
import Database from '../../../lib/db/Database';
import ChainSwapRepository from '../../../lib/db/repositories/ChainSwapRepository';
import ChannelCreationRepository from '../../../lib/db/repositories/ChannelCreationRepository';
import FeeRepository from '../../../lib/db/repositories/FeeRepository';
import PairRepository from '../../../lib/db/repositories/PairRepository';
Expand Down Expand Up @@ -292,64 +303,148 @@ describe('CommandHandler', () => {
);
});

test('should get information about (reverse) swaps', async () => {
// Submarine Swap
sendMessage(`swapinfo ${swapExample.id}`);
await wait(50);
describe('swapInfo', () => {
test('should get information about (reverse) swaps', async () => {
// Submarine Swap
sendMessage(`swapinfo ${swapExample.id}`);
await wait(50);

expect(mockSendMessage).toHaveBeenCalledTimes(1);
expect(mockSendMessage).toHaveBeenCalledWith(
`Submarine Swap \`${swapExample.id}\`:\n\`\`\`${stringify(
await SwapRepository.getSwap({ id: swapExample.id }),
)}\`\`\``,
);
expect(mockSendMessage).toHaveBeenCalledTimes(1);
expect(mockSendMessage).toHaveBeenCalledWith(
`Submarine Swap \`${swapExample.id}\`:\n\`\`\`${stringify(
await SwapRepository.getSwap({ id: swapExample.id }),
)}\`\`\``,
);

// Channel Creation Swap
sendMessage(`swapinfo ${channelSwapExample.id}`);
await wait(50);
// Channel Creation Swap
sendMessage(`swapinfo ${channelSwapExample.id}`);
await wait(50);

expect(mockSendMessage).toHaveBeenCalledTimes(2);
expect(mockSendMessage).toHaveBeenCalledWith(
`Channel Creation \`${channelSwapExample.id}\`:\n\`\`\`` +
`${stringify(
await SwapRepository.getSwap({ id: channelSwapExample.id }),
)}\n` +
`${stringify(
await ChannelCreationRepository.getChannelCreation({
swapId: channelSwapExample.id,
expect(mockSendMessage).toHaveBeenCalledTimes(2);
expect(mockSendMessage).toHaveBeenCalledWith(
`Channel Creation \`${channelSwapExample.id}\`:\n\`\`\`` +
`${stringify(
await SwapRepository.getSwap({ id: channelSwapExample.id }),
)}\n` +
`${stringify(
await ChannelCreationRepository.getChannelCreation({
swapId: channelSwapExample.id,
}),
)}\`\`\``,
);

// Reverse Swap
sendMessage(`swapinfo ${reverseSwapExample.id}`);
await wait(50);

expect(mockSendMessage).toHaveBeenCalledTimes(3);
expect(mockSendMessage).toHaveBeenCalledWith(
`Reverse Swap \`${reverseSwapExample.id}\`:\n\`\`\`${stringify(
await ReverseSwapRepository.getReverseSwap({
id: reverseSwapExample.id,
}),
)}\`\`\``,
);
);

// Reverse Swap
sendMessage(`swapinfo ${reverseSwapExample.id}`);
await wait(50);
const errorMessage = 'Could not find swap with id: ';

expect(mockSendMessage).toHaveBeenCalledTimes(3);
expect(mockSendMessage).toHaveBeenCalledWith(
`Reverse Swap \`${reverseSwapExample.id}\`:\n\`\`\`${stringify(
await ReverseSwapRepository.getReverseSwap({
id: reverseSwapExample.id,
}),
)}\`\`\``,
);
// Send an error if there is no id provided
sendMessage('swapinfo');

const errorMessage = 'Could not find swap with id: ';
expect(mockSendMessage).toHaveBeenCalledTimes(4);
expect(mockSendMessage).toHaveBeenCalledWith(errorMessage);

// Send an error if there is no id provided
sendMessage('swapinfo');
// Send an error if the swap cannot be found
const id = 'notFound';
sendMessage(`swapinfo ${id}`);

expect(mockSendMessage).toHaveBeenCalledTimes(4);
expect(mockSendMessage).toHaveBeenCalledWith(errorMessage);
await wait(50);

// Send an error if the swap cannot be found
const id = 'notFound';
sendMessage(`swapinfo ${id}`);

await wait(50);
expect(mockSendMessage).toHaveBeenCalledTimes(5);
expect(mockSendMessage).toHaveBeenCalledWith(`${errorMessage}${id}`);
});

expect(mockSendMessage).toHaveBeenCalledTimes(5);
expect(mockSendMessage).toHaveBeenCalledWith(`${errorMessage}${id}`);
test.each`
address | checksummed
${'0x1234567890123456789012345678901234567890'} | ${true}
${'0x1234567890123456789012345678901234567890'.toLowerCase()} | ${false}
`(
'should get information about funds locked for an EVM address (checksummed: $checksummed)',
async ({ address }) => {
const evmAddress = checkEvmAddress(address);
const mockReverseSwaps = [
{
id: 'reverse1',
type: SwapType.ReverseSubmarine,
claimAddress: evmAddress,
onchainAmount: 500000,
},
];
const mockChainSwaps = [
{
id: 'chain1',
type: SwapType.Chain,
sendingData: {
claimAddress: evmAddress,
amount: 300000,
},
},
{
id: 'chain2',
type: SwapType.Chain,
sendingData: {
claimAddress: 'different-address',
amount: 200000,
},
},
];

ReverseSwapRepository.getReverseSwaps = jest
.fn()
.mockResolvedValue(mockReverseSwaps);
ChainSwapRepository.getChainSwaps = jest
.fn()
.mockResolvedValue(mockChainSwaps);

sendMessage(`swapinfo ${address}`);
await wait(50);

// Verify the repository calls
expect(ReverseSwapRepository.getReverseSwaps).toHaveBeenCalledWith({
status: {
[Op.in]: [
SwapUpdateEvent.TransactionMempool,
SwapUpdateEvent.TransactionConfirmed,
],
},
claimAddress: evmAddress,
});

expect(ChainSwapRepository.getChainSwaps).toHaveBeenCalledWith({
status: {
[Op.in]: [
SwapUpdateEvent.TransactionServerMempool,
SwapUpdateEvent.TransactionServerConfirmed,
],
},
});

expect(mockSendMessage).toHaveBeenCalledTimes(3);
expect(mockSendMessage).toHaveBeenCalledWith(
`Funds locked for \`${evmAddress}\``,
);
expect(mockSendMessage).toHaveBeenCalledWith(
`Reverse Swap \`${mockReverseSwaps[0].id}\`:\n\`\`\`${stringify(
mockReverseSwaps[0],
)}\`\`\``,
);
expect(mockSendMessage).toHaveBeenCalledWith(
`Chain Swap \`${mockChainSwaps[0].id}\`:\n\`\`\`${stringify(
mockChainSwaps[0],
)}\`\`\``,
);
},
);
});

test('should get statistics', async () => {
Expand Down Expand Up @@ -481,6 +576,11 @@ describe('CommandHandler', () => {
});

test('should get pending swaps', async () => {
ReverseSwapRepository.getReverseSwaps = jest
.fn()
.mockResolvedValue([pendingReverseSwapExample]);
ChainSwapRepository.getChainSwaps = jest.fn().mockResolvedValue([]);

sendMessage('pendingswaps');
await wait(50);

Expand Down

0 comments on commit addbf7f

Please sign in to comment.