lightning
Version:
Lightning Network client library
163 lines (136 loc) • 5.19 kB
JavaScript
const asyncAuto = require('async/auto');
const {returnResult} = require('asyncjs-util');
const {isLnd} = require('./../../lnd_requests');
const defaultConfTarget = 6;
const initialConfirmationCount = 0;
const {isArray} = Array;
const {isInteger} = Number;
const lowBalanceErr = 'insufficient funds available to construct transaction';
const method = 'sendCoins';
const OPEN = 1;
const strategy = type => !type ? undefined : `STRATEGY_${type.toUpperCase()}`;
const {stringify} = JSON;
const type = 'default';
const unconfirmedConfCount = 0;
/** Send tokens in a blockchain transaction.
`utxo_selection` methods: 'largest', 'random'
Requires `onchain:write` permission
`utxo_confirmations` is not supported on LND 0.11.1 or below
`utxo_selection` is not supported in LND 0.17.5 and below
{
address: <Destination Chain Address String>
[description]: <Transaction Label String>
[fee_tokens_per_vbyte]: <Chain Fee Tokens Per Virtual Byte Number>
[inputs]: [{
transaction_id: <Unspent Transaction Id Hex String>
transaction_vout: <Unspent Transaction Output Index Number>
}]
[is_send_all]: <Send All Funds Bool>
lnd: <Authenticated LND API Object>
[log]: <Log Function>
[target_confirmations]: <Confirmations To Wait Number>
[tokens]: <Tokens To Send Number>
[utxo_confirmations]: <Minimum Confirmations for UTXO Selection Number>
[utxo_selection]: <Select UTXOs Using Method String>
[wss]: [<Web Socket Server Object>]
}
@returns via cbk or Promise
{
confirmation_count: <Total Confirmations Number>
id: <Transaction Id Hex String>
is_confirmed: <Transaction Is Confirmed Bool>
is_outgoing: <Transaction Is Outgoing Bool>
[tokens]: <Transaction Tokens Number>
}
*/
module.exports = (args, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!args.address) {
return cbk([400, 'ExpectedChainAddressToSendTo']);
}
if (!isLnd({method, type, lnd: args.lnd})) {
return cbk([400, 'ExpectedLndForChainSendRequest']);
}
if (!!args.tokens && !isInteger(args.tokens)) {
return cbk([400, 'ExpectedWholeNumberAmountToSendFundsOnChain']);
}
if (!args.tokens && !args.is_send_all) {
return cbk([400, 'MissingTokensToSendOnChain']);
}
if (!!args.tokens && !!args.is_send_all) {
return cbk([400, 'ExpectedNoTokensSpecifiedWhenSendingAllFunds']);
}
if (!!args.wss && !isArray(args.wss)) {
return cbk([400, 'ExpectedWssArrayForChainSend']);
}
if (!!args.wss && !args.log) {
return cbk([400, 'ExpectedLogFunctionForChainSendSocketAnnounce']);
}
return cbk();
},
// Determine what the confirmations to confirm should be
targetConf: ['validate', ({}, cbk) => {
// Exit early when there is a chain fee rate specified
if (!!args.fee_tokens_per_vbyte) {
return cbk();
}
return cbk(null, args.target_confirmations || defaultConfTarget);
}],
// Send coins
send: ['targetConf', ({targetConf}, cbk) => {
return args.lnd.default.sendCoins({
addr: args.address,
amount: args.tokens || undefined,
coin_selection_strategy: strategy(args.utxo_selection),
min_confs: args.utxo_confirmations || undefined,
label: args.description || undefined,
sat_per_vbyte: args.fee_tokens_per_vbyte || undefined,
send_all: args.is_send_all || undefined,
spend_unconfirmed: args.utxo_confirmations === unconfirmedConfCount,
target_conf: targetConf,
},
(err, res) => {
if (!!err && err.details === lowBalanceErr) {
return cbk([503, 'InsufficientBalanceToSendToChainAddress']);
}
if (!!err) {
return cbk([500, 'UnexpectedSendCoinsError', {err}]);
}
if (!res) {
return cbk([500, 'ExpectedResponseFromSendCoinsRequest']);
}
if (!res.txid) {
return cbk([500, 'ExpectedTransactionIdForSendCoinsTransaction']);
}
const row = {
confirmation_count: initialConfirmationCount,
id: res.txid,
is_confirmed: false,
is_outgoing: true,
tokens: !args.is_send_all ? Number(args.tokens) : undefined,
};
if (!!args.wss) {
args.wss.forEach(({clients}) => {
// Client is a Set not an array so .filter cannot be used
return clients.forEach(client => {
if (!client || client.readyState !== OPEN) {
return;
}
try {
return client.send(stringify(row));
} catch (err) {
return args.log([500, 'BroadcastFailure', {err}]);
}
});
});
}
return cbk(null, row);
});
}],
},
returnResult({reject, resolve, of: 'send'}, cbk));
});
};