Skip to content

Commit

Permalink
feat: implement Connext wallet withdrawals (#1853)
Browse files Browse the repository at this point in the history
* feat: implement Connext wallet withdrawals

* fix(cli): adjust walletwithdraw examples

* fix(grpc): include txId in WithdrawResponse

* fix(connext): convert withdraw amount to units

* fix(connext): do not allow walletwithdrawals bigger than wallet balance

* fixup! fix(connext): do not allow walletwithdrawals bigger than wallet balance

Co-authored-by: Karl Ranna <[email protected]>
  • Loading branch information
michael1011 and Karl Ranna authored Sep 1, 2020
1 parent cfdedaf commit 6f97783
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 18 deletions.
6 changes: 3 additions & 3 deletions lib/cli/commands/walletwithdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export const builder = (argv: Argv) => argv
description: 'whether to withdraw all available funds',
type: 'boolean',
})
.example('$0 withdraw 0.1 BTC 1BitcoinEaterAddressDontSendf59kuE', 'withdraws 0.1 BTC')
.example('$0 withdraw 0.1 BTC 1BitcoinEaterAddressDontSendf59kuE 20', 'withdraws 0.1 BTC using 20 sats/byte')
.example('$0 withdraw --all --currency BTC --address 1BitcoinEaterAddressDontSendf59kuE', 'withdraws all BTC');
.example('$0 walletwithdraw 0.1 BTC 1BitcoinEaterAddressDontSendf59kuE', 'withdraws 0.1 BTC')
.example('$0 walletwithdraw 0.1 BTC 1BitcoinEaterAddressDontSendf59kuE 20', 'withdraws 0.1 BTC using 20 sats/byte')
.example('$0 walletwithdraw --all --currency BTC --address 1BitcoinEaterAddressDontSendf59kuE', 'withdraws all BTC');

export const handler = async (argv: Arguments<any>) => {
const request = new WithdrawRequest();
Expand Down
44 changes: 44 additions & 0 deletions lib/connextclient/ConnextClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SwapClient, {
TradingLimits,
SwapClientInfo,
PaymentStatus,
WithdrawArguments,
} from '../swaps/SwapClient';
import { SwapDeal, CloseChannelParams, OpenChannelParams } from '../swaps/types';
import { UnitConverter } from '../utils/UnitConverter';
Expand All @@ -32,6 +33,7 @@ import {
ProvidePreimageEvent,
TransferReceivedEvent,
ConnextDepositResponse,
OnchainTransferResponse,
} from './types';
import { parseResponseBody } from '../utils/utils';
import { Observable, fromEvent, from, combineLatest, defer } from 'rxjs';
Expand Down Expand Up @@ -692,6 +694,48 @@ class ConnextClient extends SwapClient {
});
}

public withdraw = async ({
all,
currency,
amount: argAmount,
destination,
fee,
}: WithdrawArguments): Promise<string> => {
if (fee) {
// TODO: allow overwriting gas price
throw Error('setting fee for Ethereum withdrawals is not supported yet');
}

let units = '';

const { freeBalanceOnChain } = await this.getBalance(currency);

if (all) {
if (currency === 'ETH') {
// TODO: query Ether balance, subtract gas price times 21000 (gas usage of transferring Ether), and set that as amount
throw Error('withdrawing all ETH is not supported yet');
}
units = freeBalanceOnChain;
} else if (argAmount) {
const argUnits = this.unitConverter.amountToUnits({
currency,
amount: argAmount,
});
if (Number(freeBalanceOnChain) < argUnits) {
throw new Error('amount cannot be greater than wallet balance');
}
units = argUnits.toString();
}

const res = await this.sendRequest('/onchain-transfer', 'POST', {
assetId: this.getTokenAddress(currency),
amount: units,
recipient: destination,
});
const { txhash } = await parseResponseBody<OnchainTransferResponse>(res);
return txhash;
}

/** Connext client specific cleanup. */
protected disconnect = async () => {
this.setStatus(ClientStatus.Disconnected);
Expand Down
4 changes: 4 additions & 0 deletions lib/connextclient/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,7 @@ export type TransferReceivedEvent = {
units: number;
paymentId: string;
};

export type OnchainTransferResponse = {
txhash: string;
};
3 changes: 2 additions & 1 deletion lib/grpc/GrpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,9 @@ class GrpcService {
return;
}
try {
await this.service.walletWithdraw(call.request.toObject());
const txId = await this.service.walletWithdraw(call.request.toObject());
const response = new xudrpc.WithdrawResponse();
response.setTransactionId(txId);
callback(null, response);
} catch (err) {
callback(getGrpcError(err), null);
Expand Down
9 changes: 2 additions & 7 deletions lib/lndclient/LndClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as lndinvoices from '../proto/lndinvoices_pb';
import { LightningClient, WalletUnlockerClient } from '../proto/lndrpc_grpc_pb';
import * as lndrpc from '../proto/lndrpc_pb';
import swapErrors from '../swaps/errors';
import SwapClient, { ChannelBalance, ClientStatus, PaymentState, SwapClientInfo, TradingLimits } from '../swaps/SwapClient';
import SwapClient, { ChannelBalance, ClientStatus, PaymentState, SwapClientInfo, TradingLimits, WithdrawArguments } from '../swaps/SwapClient';
import { SwapDeal, CloseChannelParams, OpenChannelParams } from '../swaps/types';
import { base64ToHex, hexToUint8Array } from '../utils/utils';
import errors from './errors';
Expand Down Expand Up @@ -531,12 +531,7 @@ class LndClient extends SwapClient {
return depositAddress;
}

public withdraw = async ({ amount, destination, all = false, fee }: {
amount?: number,
destination: string,
all?: boolean,
fee?: number,
}) => {
public withdraw = async ({ amount, destination, all = false, fee }: WithdrawArguments) => {
const request = new lndrpc.SendCoinsRequest();
request.setAddr(destination);
if (fee) {
Expand Down
11 changes: 11 additions & 0 deletions lib/swaps/SwapClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ export type PaymentStatus = {
preimage?: string,
};

export type WithdrawArguments = {
currency: string,
destination: string,
amount?: number,
all?: boolean,
fee?: number,
};

interface SwapClient {
on(event: 'connectionVerified', listener: (swapClientInfo: SwapClientInfo) => void): this;
once(event: 'initialized', listener: () => void): this;
Expand Down Expand Up @@ -331,6 +339,9 @@ abstract class SwapClient extends EventEmitter {
/** Gets a deposit address. */
public abstract async deposit(): Promise<string>;

/** Withdraws from the onchain wallet of the client and returns the transaction id or transaction hash in case of Ethereum */
public abstract async withdraw(args: WithdrawArguments): Promise<string>;

public isConnected(): boolean {
return this.status === ClientStatus.ConnectionVerified;
}
Expand Down
9 changes: 2 additions & 7 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,13 +380,8 @@ class SwapClientManager extends EventEmitter {
if (!swapClient) {
throw errors.SWAP_CLIENT_NOT_FOUND(currency);
}
if (isLndClient(swapClient)) {
const txId = await swapClient.withdraw({ amount, destination, all, fee });
return txId;
} else {
// TODO: generic withdraw logic
throw new Error('currency currently not supported for on-chain withdrawal');
}

return await swapClient.withdraw({ currency, amount, destination, all, fee });
}

/**
Expand Down

0 comments on commit 6f97783

Please sign in to comment.