Skip to content

Commit d85cf87

Browse files
committed
feat(rpc/connext): deposit & openchannel calls
This refactors the `Deposit` and `OpenChannel` methods in `SwapClient` to make them generic and applicable to Connext. It implements this functionality for Connext, namely retrieiving an address for sending funds on-chain to the Connext wallet and depositing those funds into Connext channels. It also refactors the `CloseChannel` call similarly, however the Connext REST client does not currently implement a `/withdraw` endpoint for taking funds out of Connext channels. This adds dummy code to construct a withdraw payload and make a request. Withdrawing coins from the on-chain Connext wallet is not currently supported by the Connext client and so the `Withdraw` rpc call remains specific to lnd. Closes #1472. Closes #1473.
1 parent 17ef581 commit d85cf87

26 files changed

+693
-493
lines changed

Diff for: docs/api.md

+6-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: lib/cli/commands/closechannel.ts

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,50 @@
11
import { Arguments, Argv } from 'yargs';
22
import { CloseChannelRequest } from '../../proto/xudrpc_pb';
33
import { callback, loadXudClient } from '../command';
4+
import { coinsToSats } from '../utils';
45

5-
export const command = 'closechannel <node_identifier> <currency> [--force]';
6+
export const command = 'closechannel <currency> [node_identifier ] [--force]';
67

78
export const describe = 'close any payment channels with a peer';
89

910
export const builder = (argv: Argv) => argv
10-
.positional('node_identifier', {
11-
description: 'the node key or alias of the connected peer to close the channel with',
12-
type: 'string',
13-
})
1411
.positional('currency', {
1512
description: 'the ticker symbol for the currency',
1613
type: 'string',
1714
})
15+
.option('node_identifier', {
16+
description: 'the node key or alias of the connected peer to close the channel with',
17+
type: 'string',
18+
})
1819
.option('force', {
1920
type: 'boolean',
2021
description: 'whether to force close if the peer is offline',
2122
})
22-
.example('$0 closechannel 028599d05b18c0c3f8028915a17d603416f7276c822b6b2d20e71a3502bd0f9e0b BTC', 'close BTC channels by node key')
23-
.example('$0 closechannel CheeseMonkey BTC', 'close BTC channels by alias')
24-
.example('$0 closechannel CheeseMonkey BTC --force', 'force close BTC channels by alias');
23+
.option('destination', {
24+
type: 'string',
25+
description: 'the on-chain address to send funds extracted from the channel',
26+
})
27+
.option('amount', {
28+
type: 'number',
29+
description: 'for Connext only - the amount to extract from the channel',
30+
})
31+
.example('$0 closechannel BTC 028599d05b18c0c3f8028915a17d603416f7276c822b6b2d20e71a3502bd0f9e0b', 'close BTC channels by node key')
32+
.example('$0 closechannel BTC CheeseMonkey', 'close BTC channels by alias')
33+
.example('$0 closechannel BTC CheeseMonkey --force', 'force close BTC channels by alias')
34+
.example('$0 closechannel ETH --amount 0.1', '[UNIMPLEMENTED] remove 0.1 ETH from a Connext channel');
2535

2636
export const handler = async (argv: Arguments<any>) => {
2737
const request = new CloseChannelRequest();
28-
request.setNodeIdentifier(argv.node_identifier);
38+
if (argv.node_identifier) {
39+
request.setNodeIdentifier(argv.node_identifier);
40+
}
2941
request.setCurrency(argv.currency.toUpperCase());
42+
if (argv.destination) {
43+
request.setDestination(argv.destination);
44+
}
45+
if (argv.amount) {
46+
request.setAmount(coinsToSats(argv.amount));
47+
}
3048
if (argv.force) {
3149
request.setForce(argv.force);
3250
}

Diff for: lib/cli/commands/openchannel.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export const builder = (argv: Argv) => argv
2727
})
2828
.example('$0 openchannel BTC 0.1 028599d05b18c0c3f8028915a17d603416f7276c822b6b2d20e71a3502bd0f9e0b', 'open an 0.1 BTC channel by node key')
2929
.example('$0 openchannel BTC 0.1 CheeseMonkey', 'open an 0.1 BTC channel by alias')
30-
.example('$0 openchannel BTC 0.1 CheeseMonkey 0.05', 'open an 0.1 BTC channel by alias and push 0.05 to remote side');
30+
.example('$0 openchannel BTC 0.1 CheeseMonkey 0.05', 'open an 0.1 BTC channel by alias and push 0.05 to remote side')
31+
.example('$0 openchannel ETH 0.5', 'deposit 0.5 into an ETH Connext channel without specifying a remote node');
3132

3233
export const handler = async (argv: Arguments<any>) => {
3334
const request = new OpenChannelRequest();

Diff for: lib/cli/commands/deposit.ts renamed to lib/cli/commands/walletdeposit.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Arguments, Argv } from 'yargs';
22
import { DepositRequest } from '../../proto/xudrpc_pb';
33
import { callback, loadXudClient } from '../command';
44

5-
export const command = 'deposit <currency>';
5+
export const command = 'walletdeposit <currency>';
66

77
export const describe = 'gets an address to deposit funds to xud';
88

@@ -16,5 +16,5 @@ export const builder = (argv: Argv) => argv
1616
export const handler = async (argv: Arguments<any>) => {
1717
const request = new DepositRequest();
1818
request.setCurrency(argv.currency);
19-
(await loadXudClient(argv)).deposit(request, callback(argv));
19+
(await loadXudClient(argv)).walletDeposit(request, callback(argv));
2020
};

Diff for: lib/cli/commands/withdraw.ts renamed to lib/cli/commands/walletwithdraw.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Arguments, Argv } from 'yargs';
22
import { WithdrawRequest } from '../../proto/xudrpc_pb';
33
import { callback, loadXudClient } from '../command';
4+
import { coinsToSats } from '../utils';
45

5-
export const command = 'withdraw [amount] [currency] [destination] [fee]';
6+
export const command = 'walletwithdraw [amount] [currency] [destination] [fee]';
67

78
export const describe = 'withdraws on-chain funds from xud';
89

@@ -37,10 +38,10 @@ export const handler = async (argv: Arguments<any>) => {
3738
if (argv.all) {
3839
request.setAll(argv.all);
3940
} else {
40-
request.setAmount(argv.amount);
41+
request.setAmount(coinsToSats(argv.amount));
4142
}
4243
request.setDestination(argv.destination);
4344
request.setFee(argv.fee);
4445

45-
(await loadXudClient(argv)).withdraw(request, callback(argv));
46+
(await loadXudClient(argv)).walletWithdraw(request, callback(argv));
4647
};

Diff for: lib/connextclient/ConnextClient.ts

+21-16
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SwapClient, {
1313
SwapClientInfo,
1414
PaymentStatus,
1515
} from '../swaps/SwapClient';
16-
import { SwapDeal } from '../swaps/types';
16+
import { SwapDeal, CloseChannelParams, OpenChannelParams } from '../swaps/types';
1717
import { UnitConverter } from '../utils/UnitConverter';
1818
import errors, { errorCodes } from './errors';
1919
import {
@@ -547,28 +547,33 @@ class ConnextClient extends SwapClient {
547547
};
548548
}
549549

550-
/**
551-
* Deposits funds to a node
552-
*/
553-
public deposit = async (
554-
{ currency, units }:
555-
{ currency: string, units: number },
556-
) => {
550+
public deposit = async () => {
551+
const clientConfig = await this.getClientConfig();
552+
return clientConfig.signerAddress;
553+
}
554+
555+
public openChannel = async ({ currency, units }: OpenChannelParams) => {
556+
if (!currency) {
557+
throw errors.CURRENCY_MISSING;
558+
}
557559
const assetId = this.getTokenAddress(currency);
558560
await this.sendRequest('/deposit', 'POST', {
559561
assetId,
560562
amount: BigInt(units).toString(),
561563
});
562564
}
563565

564-
public async openChannel() {}
565-
566-
/**
567-
* Closes a payment client.
568-
* @param multisigAddress the address of the client to close
569-
*/
570-
public closeChannel = async (): Promise<void> => {
571-
// not relevant for connext
566+
public closeChannel = async ({ units, currency, destination }: CloseChannelParams): Promise<void> => {
567+
if (!currency) {
568+
throw errors.CURRENCY_MISSING;
569+
}
570+
const amount = units || (await this.channelBalance(currency)).balance;
571+
// TODO: connext-api-client withdraw endpoint not currently implemented
572+
await this.sendRequest('/withdraw', 'POST', {
573+
recipient: destination,
574+
amount: BigInt(amount).toString(),
575+
assetId: this.tokenAddresses.get(currency),
576+
});
572577
}
573578

574579
/**

Diff for: lib/grpc/GrpcService.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -343,30 +343,31 @@ class GrpcService {
343343
}
344344

345345
/**
346-
* See [[Service.deposit]]
346+
* See [[Service.walletDeposit]]
347347
*/
348-
public deposit: grpc.handleUnaryCall<xudrpc.DepositRequest, xudrpc.DepositResponse> = async (call, callback) => {
348+
public walletDeposit: grpc.handleUnaryCall<xudrpc.DepositRequest, xudrpc.DepositResponse> = async (call, callback) => {
349349
if (!this.isReady(this.service, callback)) {
350350
return;
351351
}
352352
try {
353-
await this.service.deposit(call.request.toObject());
353+
const address = await this.service.walletDeposit(call.request.toObject());
354354
const response = new xudrpc.DepositResponse();
355+
response.setAddress(address);
355356
callback(null, response);
356357
} catch (err) {
357358
callback(getGrpcError(err), null);
358359
}
359360
}
360361

361362
/**
362-
* See [[Service.withdraw]]
363+
* See [[Service.walletWithdraw]]
363364
*/
364-
public withdraw: grpc.handleUnaryCall<xudrpc.WithdrawRequest, xudrpc.WithdrawResponse> = async (call, callback) => {
365+
public walletWithdraw: grpc.handleUnaryCall<xudrpc.WithdrawRequest, xudrpc.WithdrawResponse> = async (call, callback) => {
365366
if (!this.isReady(this.service, callback)) {
366367
return;
367368
}
368369
try {
369-
await this.service.withdraw(call.request.toObject());
370+
await this.service.walletWithdraw(call.request.toObject());
370371
const response = new xudrpc.WithdrawResponse();
371372
callback(null, response);
372373
} catch (err) {

Diff for: lib/grpc/getGrpcError.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const getGrpcError = (err: any) => {
2020
case p2pErrorCodes.NODE_TOR_ADDRESS:
2121
case orderErrorCodes.MIN_QUANTITY_VIOLATED:
2222
case orderErrorCodes.QUANTITY_DOES_NOT_MATCH:
23+
case swapErrorCodes.REMOTE_IDENTIFIER_MISSING:
2324
code = status.INVALID_ARGUMENT;
2425
break;
2526
case orderErrorCodes.PAIR_DOES_NOT_EXIST:

Diff for: lib/lndclient/LndClient.ts

+36-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { LightningClient, WalletUnlockerClient } from '../proto/lndrpc_grpc_pb';
1010
import * as lndrpc from '../proto/lndrpc_pb';
1111
import swapErrors from '../swaps/errors';
1212
import SwapClient, { ChannelBalance, ClientStatus, PaymentState, SwapClientInfo, TradingLimits } from '../swaps/SwapClient';
13-
import { SwapDeal } from '../swaps/types';
13+
import { SwapDeal, CloseChannelParams, OpenChannelParams } from '../swaps/types';
1414
import { base64ToHex, hexToUint8Array } from '../utils/utils';
1515
import errors from './errors';
1616
import { Chain, ChannelCount, ClientMethods, LndClientConfig, LndInfo } from './types';
@@ -519,8 +519,13 @@ class LndClient extends SwapClient {
519519
return this.unaryCall<lndrpc.ClosedChannelsRequest, lndrpc.ClosedChannelsResponse>('closedChannels', new lndrpc.ClosedChannelsRequest());
520520
}
521521

522+
public deposit = async () => {
523+
const depositAddress = await this.newAddress();
524+
return depositAddress;
525+
}
526+
522527
public withdraw = async ({ amount, destination, all = false, fee }: {
523-
amount: number,
528+
amount?: number,
524529
destination: string,
525530
all?: boolean,
526531
fee?: number,
@@ -532,7 +537,7 @@ class LndClient extends SwapClient {
532537
}
533538
if (all) {
534539
request.setSendAll(all);
535-
} else {
540+
} else if (amount) {
536541
request.setAmount(amount);
537542
}
538543
const withdrawResponse = await this.unaryCall<lndrpc.SendCoinsRequest, lndrpc.SendCoinsResponse>('sendCoins', request);
@@ -653,7 +658,7 @@ class LndClient extends SwapClient {
653658
/**
654659
* Gets a new address for the internal lnd wallet.
655660
*/
656-
public newAddress = async (addressType = lndrpc.AddressType.WITNESS_PUBKEY_HASH) => {
661+
private newAddress = async (addressType = lndrpc.AddressType.WITNESS_PUBKEY_HASH) => {
657662
const request = new lndrpc.NewAddressRequest();
658663
request.setType(addressType);
659664
const newAddressResponse = await this.unaryCall<lndrpc.NewAddressRequest, lndrpc.NewAddressResponse>('newAddress', request);
@@ -739,14 +744,17 @@ class LndClient extends SwapClient {
739744
* Opens a channel given peerPubKey and amount.
740745
*/
741746
public openChannel = async (
742-
{ peerIdentifier: peerPubKey, units, uris, pushUnits = 0 }:
743-
{ peerIdentifier: string, units: number, uris?: string[], pushUnits?: number },
747+
{ remoteIdentifier, units, uris, pushUnits = 0 }: OpenChannelParams,
744748
): Promise<void> => {
749+
if (!remoteIdentifier) {
750+
// TODO: better handling for for unrecognized peers & force closing channels
751+
throw new Error('peer not connected to swap client');
752+
}
745753
if (uris) {
746754
await this.connectPeerAddresses(uris);
747755
}
748756

749-
await this.openChannelSync(peerPubKey, units, pushUnits);
757+
await this.openChannelSync(remoteIdentifier, units, pushUnits);
750758
}
751759

752760
/**
@@ -1025,13 +1033,32 @@ class LndClient extends SwapClient {
10251033
}
10261034

10271035
/**
1028-
* Attempts to close an open channel.
1036+
* Closes any payment channels with a specified node.
10291037
*/
1030-
public closeChannel = (fundingTxId: string, outputIndex: number, force: boolean): Promise<void> => {
1038+
public closeChannel = async ({ remoteIdentifier, force = false }: CloseChannelParams) => {
1039+
if (remoteIdentifier === undefined) {
1040+
throw swapErrors.REMOTE_IDENTIFIER_MISSING;
1041+
}
1042+
const channels = (await this.listChannels()).getChannelsList();
1043+
const closePromises: Promise<void>[] = [];
1044+
channels.forEach((channel) => {
1045+
if (channel.getRemotePubkey() === remoteIdentifier) {
1046+
const [fundingTxId, outputIndex] = channel.getChannelPoint().split(':');
1047+
const closePromise = this.closeChannelSync(fundingTxId, Number(outputIndex), force);
1048+
closePromises.push(closePromise);
1049+
}
1050+
});
1051+
await Promise.all(closePromises);
1052+
}
1053+
1054+
/** A synchronous helper method for the closeChannel call */
1055+
public closeChannelSync = (fundingTxId: string, outputIndex: number, force: boolean): Promise<void> => {
10311056
return new Promise<void>((resolve, reject) => {
10321057
if (!this.lightning) {
10331058
throw(errors.UNAVAILABLE(this.currency, this.status));
10341059
}
1060+
1061+
// TODO: set delivery_address parameter after upgrading to 0.10+ lnd API proto definition
10351062
const request = new lndrpc.CloseChannelRequest();
10361063
const channelPoint = new lndrpc.ChannelPoint();
10371064
channelPoint.setFundingTxidStr(fundingTxId);

0 commit comments

Comments
 (0)