Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tapd): Add support for litd v0.14.0-alpha #1039

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions docker/litd/src.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# Start with a NodeJS base image that also contains yarn.
FROM node:16.20.2-buster-slim as nodejsbuilder
FROM node:22.8.0-alpine@sha256:bec0ea49c2333c429b62e74e91f8ba1201b060110745c3a12ff957cd51b363c6 as nodejsbuilder

ARG LITD_VERSION

RUN apt-get update -y \
&& apt-get install -y git
RUN apk add --no-cache --update git

# Copy in the local repository to build from.
RUN git clone --branch ${LITD_VERSION} https://github.com/lightninglabs/lightning-terminal.git /go/src/github.com/lightninglabs/lightning-terminal/
Expand Down
45 changes: 25 additions & 20 deletions electron/tapd/tapdProxyServer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IpcMain } from 'electron';
import { debug } from 'electron-log';
import { readFile } from 'fs-extra';
import * as LND from '@lightningpolar/lnd-api';
import * as TAPD from '@lightningpolar/tapd-api';
import {
convertUInt8ArraysToHex,
Expand Down Expand Up @@ -121,28 +122,33 @@ const fundChannel = async (args: {
return await channels.fundChannel(args.req);
};

const addAssetBuyOrder = async (args: {
const addInvoice = async (args: {
node: TapdNode;
req: TAPD.AddAssetBuyOrderRequestPartial;
}): Promise<TAPD.AddAssetBuyOrderResponse> => {
const { rfq } = await getRpc(args.node);
return await rfq.addAssetBuyOrder(args.req);
};

const addAssetSellOrder = async (args: {
node: TapdNode;
req: TAPD.AddAssetSellOrderRequestPartial;
}): Promise<TAPD.AddAssetSellOrderResponse> => {
const { rfq } = await getRpc(args.node);
return await rfq.addAssetSellOrder(args.req);
req: TAPD.AddInvoiceRequestPartial;
}): Promise<TAPD.tapchannelrpc.AddInvoiceResponse> => {
const { channels } = await getRpc(args.node);
return await channels.addInvoice(args.req);
};

const encodeCustomRecords = async (args: {
const sendPayment = async (args: {
node: TapdNode;
req: TAPD.EncodeCustomRecordsRequestPartial;
}): Promise<TAPD.EncodeCustomRecordsResponse> => {
req: TAPD.tapchannelrpc.SendPaymentRequestPartial;
}): Promise<TAPD.tapchannelrpc.SendPaymentResponse> => {
const { channels } = await getRpc(args.node);
return await channels.encodeCustomRecords(args.req);
return new Promise((resolve, reject) => {
const stream = channels.sendPayment(args.req);
stream.on('data', (res: TAPD.tapchannelrpc.SendPaymentResponse) => {
// this callback will be called multiple times for each payment attempt. We only
// want to resolve the promise when the payment is successful or failed.
if (res.paymentResult?.status === LND._lnrpc_Payment_PaymentStatus.SUCCEEDED) {
resolve(res);
} else if (res.paymentResult?.status === LND._lnrpc_Payment_PaymentStatus.FAILED) {
reject(new Error(`Payment failed: ${res.paymentResult?.failureReason}`));
}
});
stream.on('error', err => reject(err));
stream.on('end', () => reject(new Error('Stream ended without a payment result')));
});
};

/**
Expand All @@ -163,9 +169,8 @@ const listeners: {
[ipcChannels.tapd.assetLeaves]: assetLeaves,
[ipcChannels.tapd.syncUniverse]: syncUniverse,
[ipcChannels.tapd.fundChannel]: fundChannel,
[ipcChannels.tapd.addAssetBuyOrder]: addAssetBuyOrder,
[ipcChannels.tapd.addAssetSellOrder]: addAssetSellOrder,
[ipcChannels.tapd.encodeCustomRecords]: encodeCustomRecords,
[ipcChannels.tapd.addInvoice]: addInvoice,
[ipcChannels.tapd.sendPayment]: sendPayment,
};

/**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"dependencies": {
"@lightningpolar/litd-api": "0.13.99-alpha",
"@lightningpolar/lnd-api": "0.18.99-beta.pre3",
"@lightningpolar/tapd-api": "0.4.0-alpha",
"@lightningpolar/tapd-api": "0.4.2-alpha.pre2",
"@types/lodash": "4.17.12",
"archiver": "7.0.1",
"docker-compose": "0.24.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LightningNodeChannelAsset } from 'lib/lightning/types';
import { Network } from 'types';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createNetwork } from 'utils/network';
import { createNetwork, mapToTapd } from 'utils/network';
import {
defaultStateChannel,
getNetwork,
Expand Down Expand Up @@ -161,14 +161,15 @@ describe('CreateInvoiceModal', () => {
lightningServiceMock.getChannels.mockResolvedValue([
defaultStateChannel({ assets: [asset] }),
]);
lightningServiceMock.createInvoice.mockResolvedValue('lnbc1invoice');
lightningServiceMock.decodeInvoice.mockResolvedValue({
amountMsat: '20000',
expiry: '3600',
paymentHash: 'payment-hash',
});
tapServiceMock.assetRoots.mockResolvedValue([
{ id: 'abcd', name: 'test asset', rootSum: 100 },
]);
tapServiceMock.addAssetBuyOrder.mockResolvedValue({
scid: 'abcd',
askPrice: '100',
});
tapServiceMock.addInvoice.mockResolvedValue('lnbc1invoice');
});

it('should display the asset dropdown', async () => {
Expand Down Expand Up @@ -200,22 +201,14 @@ describe('CreateInvoiceModal', () => {
expect(await findByText('Successfully Created the Invoice')).toBeInTheDocument();
expect(getByDisplayValue('lnbc1invoice')).toBeInTheDocument();
const node = network.nodes.lightning[0];
expect(lightningServiceMock.createInvoice).toHaveBeenCalledWith(node, 200, '', {
msats: '20000',
nodeId: '',
scid: 'abcd',
});
});

it('should display a warning for large asset amounts', async () => {
const { getByLabelText, findByText, changeSelect } = await renderComponent();
expect(await findByText('Node')).toBeInTheDocument();
changeSelect('Asset to Receive', 'test asset');
fireEvent.change(getByLabelText('Amount'), { target: { value: '10000' } });
const warning =
'With the default mock exchange rate of 100 sats to 1 asset, ' +
'it is best to use amounts below 5,000 to reduce the chances of payment failures.';
expect(await findByText(warning)).toBeInTheDocument();
const tapNode = mapToTapd(node);
expect(tapServiceMock.addInvoice).toHaveBeenCalledWith(
tapNode,
'abcd',
200,
'',
3600,
);
});

it('should display an error when creating an asset invoice with a high balance', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ const Styled = {
`,
};

const ASSET_WARNING_THRESHOLD = 5000;

interface FormValues {
node: string;
assetId: string;
Expand All @@ -65,7 +63,6 @@ const CreateInvoiceModal: React.FC<Props> = ({ network }) => {

const [form] = Form.useForm();
const assetId = Form.useWatch<string>('assetId', form) || 'sats';
const selectedAmount = Form.useWatch<number>('amount', form) || 0;
const selectedNode = Form.useWatch<string>('node', form) || '';

const isLitd = network.nodes.lightning.some(
Expand Down Expand Up @@ -116,7 +113,7 @@ const CreateInvoiceModal: React.FC<Props> = ({ network }) => {

const asset = assets.find(a => a.id === assetId) as LightningNodeChannelAsset;
const balance = parseInt(asset.remoteBalance);
return Math.min(Math.floor(balance / 2), ASSET_WARNING_THRESHOLD);
return Math.floor(balance / 2);
},
[assets, isLitd],
);
Expand Down Expand Up @@ -181,11 +178,6 @@ const CreateInvoiceModal: React.FC<Props> = ({ network }) => {
name="amount"
label={l('amountLabel')}
rules={[{ required: true, message: l('cmps.forms.required') }]}
help={
assetId === 'sats' || selectedAmount <= ASSET_WARNING_THRESHOLD
? undefined
: l('amountHelp', { threshold: format(ASSET_WARNING_THRESHOLD) })
}
>
<InputNumber<number>
min={1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('OpenChannelModal', () => {
lightningServiceMock.getBalances.mockResolvedValue(balances('0'));
const { findByText } = await renderComponent();
expect(
await findByText('Deposit enough funds to alice to open the channel'),
await findByText('Deposit enough sats to alice to fund the channel'),
).toBeInTheDocument();
});
});
Expand All @@ -203,7 +203,7 @@ describe('OpenChannelModal', () => {
it('should open a channel successfully', async () => {
const { getByText, getByLabelText, store } = await renderComponent('bob', 'alice');
fireEvent.change(getByLabelText('Capacity'), { target: { value: '1000' } });
fireEvent.click(getByLabelText('Deposit enough funds to bob to open the channel'));
fireEvent.click(getByLabelText('Deposit enough sats to bob to fund the channel'));
fireEvent.click(getByText('Open Channel'));
await waitFor(() => {
expect(store.getState().modals.openChannel.visible).toBe(false);
Expand All @@ -221,7 +221,7 @@ describe('OpenChannelModal', () => {
it('should open a private channel successfully', async () => {
const { getByText, getByLabelText, store } = await renderComponent('bob', 'alice');
fireEvent.change(getByLabelText('Capacity'), { target: { value: '1000' } });
fireEvent.click(getByLabelText('Deposit enough funds to bob to open the channel'));
fireEvent.click(getByLabelText('Deposit enough sats to bob to fund the channel'));
fireEvent.click(getByText('Make the channel private'));
fireEvent.click(getByText('Open Channel'));
await waitFor(() => {
Expand Down Expand Up @@ -263,7 +263,7 @@ describe('OpenChannelModal', () => {
fireEvent.change(getByLabelText('Capacity'), { target: { value: '1000' } });
changeSelect('Destination', 'alice');
fireEvent.click(
await findByLabelText('Deposit enough funds to bob to open the channel'),
await findByLabelText('Deposit enough sats to bob to fund the channel'),
);
fireEvent.click(getByText('Open Channel'));
await waitFor(() => {
Expand Down Expand Up @@ -310,16 +310,11 @@ describe('OpenChannelModal', () => {
total: '300',
});
bitcoindServiceMock.sendFunds.mockResolvedValue('txid');
lightningServiceMock.createInvoice.mockResolvedValue('lnbc1invoice');
tapServiceMock.listBalances.mockResolvedValue([
defaultTapBalance({ id: 'abcd', name: 'test asset', balance: '1000' }),
defaultTapBalance({ id: 'efgh', name: 'other asset', balance: '5000' }),
]);
tapServiceMock.syncUniverse.mockResolvedValue({ syncedUniverses: [] });
tapServiceMock.addAssetBuyOrder.mockResolvedValue({
scid: 'abcd',
askPrice: '100',
});
});

it('should display the asset dropdown', async () => {
Expand Down
25 changes: 11 additions & 14 deletions src/components/designer/lightning/actions/PayInvoiceModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LightningNodeChannelAsset } from 'lib/lightning/types';
import { Network } from 'types';
import { initChartFromNetwork } from 'utils/chart';
import { defaultRepoState } from 'utils/constants';
import { createNetwork } from 'utils/network';
import { createNetwork, mapToTapd } from 'utils/network';
import {
defaultStateChannel,
getNetwork,
Expand Down Expand Up @@ -163,18 +163,13 @@ describe('PayInvoiceModal', () => {
amountMsat: '400000',
expiry: '123456',
});
lightningServiceMock.payInvoice.mockResolvedValue({
preimage: 'preimage',
amount: 1000,
destination: 'asdf',
});
tapServiceMock.assetRoots.mockResolvedValue([
{ id: 'abcd', name: 'test asset', rootSum: 100 },
]);
tapServiceMock.addAssetSellOrder.mockResolvedValue({
id: 'abcd',
bidPrice: '100',
scid: '12345',
tapServiceMock.sendPayment.mockResolvedValue({
preimage: 'preimage',
amount: 1000,
destination: 'asdf',
});
});

Expand All @@ -195,11 +190,13 @@ describe('PayInvoiceModal', () => {
expect(store.getState().modals.payInvoice.visible).toBe(false);
});
const node = network.nodes.lightning[1];
expect(lightningServiceMock.payInvoice).toHaveBeenCalledWith(
node,
const tapdNode = mapToTapd(node);
expect(tapServiceMock.sendPayment).toHaveBeenCalledWith(
tapdNode,
'abcd',
'lnbc1',
400,
undefined,
400000,
'',
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ describe('MintAssetModal', () => {
total: '0',
});
const { findByText, getByText } = await renderComponent();
fireEvent.click(getByText('Deposit enough funds to alice'));
fireEvent.click(getByText('Deposit enough sats to alice to pay on-chain fees'));
expect(
await findByText('Insufficient balance on lnd node alice'),
).toBeInTheDocument();
Expand Down
10 changes: 6 additions & 4 deletions src/components/designer/tap/actions/SendAssetModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,17 @@ describe('SendAssetModal', () => {
expect(
await findByText('Insufficient balance on lnd node alice'),
).toBeInTheDocument();
expect(getByText('Deposit enough funds to alice')).toBeInTheDocument();
expect(
getByText('Deposit enough sats to alice to pay on-chain fees'),
).toBeInTheDocument();
});

it('should disable the alert when auto deposit is enabled', async () => {
const { queryByText, getByText } = await renderComponent();
await waitFor(() => {
expect(lightningServiceMock.getBalances).toBeCalled();
});
fireEvent.click(getByText('Deposit enough funds to alice'));
fireEvent.click(getByText('Deposit enough sats to alice to pay on-chain fees'));
expect(
queryByText('Insufficient balance on lnd node alice'),
).not.toBeInTheDocument();
Expand All @@ -163,7 +165,7 @@ describe('SendAssetModal', () => {
await waitFor(() => {
expect(tapServiceMock.decodeAddress).toBeCalled();
});
fireEvent.click(getByText('Deposit enough funds to alice'));
fireEvent.click(getByText('Deposit enough sats to alice to pay on-chain fees'));

expect(getByText('Address Info')).toBeInTheDocument();
fireEvent.click(getByText('Send'));
Expand Down Expand Up @@ -199,7 +201,7 @@ describe('SendAssetModal', () => {
await waitFor(() => {
expect(tapServiceMock.decodeAddress).toBeCalled();
});
fireEvent.click(getByText('Deposit enough funds to carol'));
fireEvent.click(getByText('Deposit enough sats to carol to pay on-chain fees'));

expect(getByText('Address Info')).toBeInTheDocument();
fireEvent.click(getByText('Send'));
Expand Down
9 changes: 4 additions & 5 deletions src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@
"cmps.designer.lightning.actions.CreateInvoiceModal.nodeLabel": "Node",
"cmps.designer.lightning.actions.CreateInvoiceModal.assetLabel": "Asset to Receive",
"cmps.designer.lightning.actions.CreateInvoiceModal.amountLabel": "Amount",
"cmps.designer.lightning.actions.CreateInvoiceModal.amountHelp": "With the default mock exchange rate of 100 sats to 1 asset, it is best to use amounts below {{threshold}} to reduce the chances of payment failures.",
"cmps.designer.lightning.actions.CreateInvoiceModal.cancelBtn": "Cancel",
"cmps.designer.lightning.actions.CreateInvoiceModal.okBtn": "Create Invoice",
"cmps.designer.lightning.actions.CreateInvoiceModal.submitError": "Unable to create the Invoice",
Expand Down Expand Up @@ -224,7 +223,7 @@
"cmps.designer.lightning.actions.OpenChannelModal.dest": "Destination",
"cmps.designer.lightning.actions.OpenChannelModal.asset": "Asset",
"cmps.designer.lightning.actions.OpenChannelModal.capacityLabel": "Capacity",
"cmps.designer.lightning.actions.OpenChannelModal.deposit": "Deposit enough funds to {{selectedFrom}} to open the channel",
"cmps.designer.lightning.actions.OpenChannelModal.deposit": "Deposit enough sats to {{selectedFrom}} to fund the channel",
"cmps.designer.lightning.actions.OpenChannelModal.private": "Make the channel private",
"cmps.designer.lightning.actions.OpenChannelModal.sameNodesWarnMsg": "Cannot open a channel from a node to itself",
"cmps.designer.lightning.actions.OpenChannelModal.cancelBtn": "Cancel",
Expand Down Expand Up @@ -390,7 +389,7 @@
"cmps.designer.tap.actions.MintAssetModal.metaDataPlaceholder": "Optional",
"cmps.designer.tap.actions.MintAssetModal.mintSuccess": "Successfully minted {{amt}} {{name}}",
"cmps.designer.tap.actions.MintAssetModal.mintError": "Failed to mint {{amount}} {{name}}",
"cmps.designer.tap.actions.MintAssetModal.deposit": "Deposit enough funds to {{selectedFrom}}",
"cmps.designer.tap.actions.MintAssetModal.deposit": "Deposit enough sats to {{selectedFrom}} to pay on-chain fees",
"cmps.designer.tap.actions.MintAssetModal.lndBalanceError": "Insufficient balance on lnd node {{lndNode}}",
"cmps.designer.tap.actions.NewAddressButton.title": "Addresses",
"cmps.designer.tap.actions.NewAddressButton.newAddress": "Create Asset Address",
Expand All @@ -415,7 +414,7 @@
"cmps.designer.tap.actions.SendAssetButton.send": "Send Asset On-chain",
"cmps.designer.tap.actions.SendAssetModal.title": "Send Asset from {{name}}",
"cmps.designer.tap.actions.SendAssetModal.address": "TAP Address",
"cmps.designer.tap.actions.SendAssetModal.deposit": "Deposit enough funds to {{selectedFrom}}",
"cmps.designer.tap.actions.SendAssetModal.deposit": "Deposit enough sats to {{selectedFrom}} to pay on-chain fees",
"cmps.designer.tap.actions.SendAssetModal.lndBalanceError": "Insufficient balance on lnd node {{lndNode}}",
"cmps.designer.tap.actions.SendAssetModal.okBtn": "Send",
"cmps.designer.tap.actions.SendAssetModal.cancelBtn": "Cancel",
Expand All @@ -435,7 +434,7 @@
"cmps.designer.tap.InfoTab.numAssets": "Number of Assets",
"cmps.designer.tap.InfoTab.startError": "Unable to connect to {{implementation}} node",
"cmps.designer.tap.AssetsList.title": "Assets",
"cmps.designer.tap.AssetsList.noAssets": "No assets have been created.",
"cmps.designer.tap.AssetsList.noAssets": "This node does not have any assets in its on-chain wallet.",
"cmps.designer.tap.TapDetails.info": "Info",
"cmps.designer.tap.TapDetails.connect": "Connect",
"cmps.designer.tap.TapDetails.actions": "Actions",
Expand Down
Loading