paid-services 3.16.3 → 3.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/groups/ask_for_group_details.js +160 -0
- package/groups/ask_to_confirm_group_join.js +140 -0
- package/groups/assemble/assemble_unsigned_psbt.js +169 -0
- package/groups/assemble/coordinate_group.js +383 -0
- package/groups/assemble/index.js +3 -0
- package/groups/assemble_channel_group.js +178 -0
- package/groups/attach_to_channel_group.js +80 -0
- package/groups/coordinate_channel_group.js +122 -0
- package/groups/funding/confirm_incoming_channel.js +108 -0
- package/groups/funding/index.js +9 -0
- package/groups/funding/propose_group_channel.js +167 -0
- package/groups/funding/sign_and_fund_group_channel.js +188 -0
- package/groups/index.js +3 -0
- package/groups/join_channel_group.js +193 -0
- package/groups/manage_group_join.js +96 -0
- package/groups/members/index.js +3 -0
- package/groups/members/partners_from_members.js +26 -0
- package/groups/messages/decode_connected_records.js +56 -0
- package/groups/messages/decode_group_details.js +99 -0
- package/groups/messages/decode_partners_records.js +57 -0
- package/groups/messages/decode_pending_proposal.js +167 -0
- package/groups/messages/decode_signed_funding.js +42 -0
- package/groups/messages/decode_signed_records.js +56 -0
- package/groups/messages/decode_unsigned_funding.js +43 -0
- package/groups/messages/encode_connected_records.js +26 -0
- package/groups/messages/encode_group_details.js +32 -0
- package/groups/messages/encode_partners_records.js +22 -0
- package/groups/messages/encode_pending_proposal.js +91 -0
- package/groups/messages/encode_signed_records.js +26 -0
- package/groups/messages/encode_unsigned_funding.js +19 -0
- package/groups/messages/index.js +29 -0
- package/groups/p2p/find_group_partners.js +122 -0
- package/groups/p2p/get_group_details.js +81 -0
- package/groups/p2p/index.js +15 -0
- package/groups/p2p/peer_with_partners.js +57 -0
- package/groups/p2p/register_group_connected.js +107 -0
- package/groups/p2p/register_pending_open.js +218 -0
- package/groups/p2p/register_signed_open.js +118 -0
- package/index.js +2 -0
- package/package.json +3 -3
- package/service_types.json +11 -1
- package/test/integration/test_group.js +174 -0
- package/trades/buy_channel.js +1 -1
- package/trades/buy_preimage.js +1 -1
- package/trades/create_channel_sale.js +6 -6
- package/trades/create_trade.js +7 -7
- package/trades/find_trade.js +1 -1
- package/trades/manage_trade.js +4 -4
- package/trades/manage_trades.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getChainBalance} = require('ln-service');
|
|
3
|
+
const {getChainFeeRate} = require('ln-service');
|
|
4
|
+
const {returnResult} = require('asyncjs-util');
|
|
5
|
+
|
|
6
|
+
const defaultChannelCapacity = 5e6;
|
|
7
|
+
const defaultGroupSize = 3;
|
|
8
|
+
const {floor} = Math;
|
|
9
|
+
const isNumber = n => !isNaN(n);
|
|
10
|
+
const isOdd = n => !!(n % 2);
|
|
11
|
+
const maxChannelSize = 21e14;
|
|
12
|
+
const minChannelSize = 2e4;
|
|
13
|
+
const maxGroupSize = 420;
|
|
14
|
+
const minGroupSize = 3;
|
|
15
|
+
const {round} = Math;
|
|
16
|
+
|
|
17
|
+
/** Ask for new group details to create a group
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
ask: <Ask Function>
|
|
21
|
+
lnd: <Authenticated LND API Object>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@returns via cbk or Promise
|
|
25
|
+
{
|
|
26
|
+
capacity: <Channel Capacity Tokens Number>
|
|
27
|
+
count: <Group Members Number>
|
|
28
|
+
rate: <Chain Fee Tokens Per VByte Number>
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
31
|
+
module.exports = ({ask, lnd}, cbk) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
return asyncAuto({
|
|
34
|
+
// Check arguments
|
|
35
|
+
validate: cbk => {
|
|
36
|
+
if (!ask) {
|
|
37
|
+
return cbk([400, 'ExpectedAskFunctionToAskForGroupDetails']);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!lnd) {
|
|
41
|
+
return cbk([400, 'ExpectedAuthenticatedLndToAskForGroupDetails']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return cbk();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Get the wallet balance to make sure there are enough funds to join
|
|
48
|
+
getBalance: ['validate', ({}, cbk) => getChainBalance({lnd}, cbk)],
|
|
49
|
+
|
|
50
|
+
// Ask for how big the channels should be
|
|
51
|
+
askForCapacity: ['getBalance', ({getBalance}, cbk) => {
|
|
52
|
+
return ask({
|
|
53
|
+
default: defaultChannelCapacity,
|
|
54
|
+
name: 'capacity',
|
|
55
|
+
message: 'Channel capacity?',
|
|
56
|
+
validate: input => {
|
|
57
|
+
if (!input || !isNumber(input)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (round(Number(input)) !== Number(input)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Number(input) > getBalance.chain_balance) {
|
|
66
|
+
return `Current chain balance is ${getBalance.chain_balance}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (Number(input) < minChannelSize) {
|
|
70
|
+
return `Minimum channel size is ${minChannelSize}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isOdd(Number(input))) {
|
|
74
|
+
return 'Channel capacity must be even';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
({capacity}) => cbk(null, Number(capacity)));
|
|
81
|
+
}],
|
|
82
|
+
|
|
83
|
+
// Get the chain fee rate to use for a default in the chain fee query
|
|
84
|
+
getFeeRate: ['validate', ({}, cbk) => getChainFeeRate({lnd}, cbk)],
|
|
85
|
+
|
|
86
|
+
// Ask for how many group members there should be
|
|
87
|
+
askForCount: ['askForCapacity', ({askForCapacity}, cbk) => {
|
|
88
|
+
return ask({
|
|
89
|
+
default: defaultGroupSize,
|
|
90
|
+
name: 'size',
|
|
91
|
+
message: 'Total number of group members?',
|
|
92
|
+
validate: input => {
|
|
93
|
+
if (!input || !isNumber(input)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Do not allow fractional members
|
|
98
|
+
if (round(Number(input)) !== Number(input)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Do not allow too many members
|
|
103
|
+
if (Number(input) > maxGroupSize) {
|
|
104
|
+
return `The maximum group size is ${maxGroupSize}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Do not allow too few members
|
|
108
|
+
if (Number(input) < minGroupSize) {
|
|
109
|
+
return `The minimum group size is ${minGroupSize}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
({size}) => cbk(null, size));
|
|
116
|
+
}],
|
|
117
|
+
|
|
118
|
+
// Ask for a chain fee rate
|
|
119
|
+
askForFeeRate: [
|
|
120
|
+
'askForCount',
|
|
121
|
+
'getFeeRate',
|
|
122
|
+
({askForCount, getFeeRate}, cbk) =>
|
|
123
|
+
{
|
|
124
|
+
return ask({
|
|
125
|
+
default: floor(getFeeRate.tokens_per_vbyte),
|
|
126
|
+
name: 'rate',
|
|
127
|
+
message: 'Chain fee per vbyte?',
|
|
128
|
+
validate: input => {
|
|
129
|
+
if (!input || !isNumber(input) || !Number(input)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Do not allow fractional fee rates
|
|
134
|
+
if (round(Number(input)) !== Number(input)) {
|
|
135
|
+
return 'Fractional fee rate setting is not supported';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
({rate}) => cbk(null, rate));
|
|
142
|
+
}],
|
|
143
|
+
|
|
144
|
+
// Final group details
|
|
145
|
+
group: [
|
|
146
|
+
'askForCapacity',
|
|
147
|
+
'askForCount',
|
|
148
|
+
'askForFeeRate',
|
|
149
|
+
({askForCapacity, askForCount, askForFeeRate}, cbk) =>
|
|
150
|
+
{
|
|
151
|
+
return cbk(null, {
|
|
152
|
+
capacity: askForCapacity,
|
|
153
|
+
count: askForCount,
|
|
154
|
+
rate: askForFeeRate,
|
|
155
|
+
});
|
|
156
|
+
}],
|
|
157
|
+
},
|
|
158
|
+
returnResult({reject, resolve, of: 'group'}, cbk));
|
|
159
|
+
});
|
|
160
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {connectPeer} = require('ln-sync');
|
|
3
|
+
const {getChainBalance} = require('ln-service');
|
|
4
|
+
const {getNodeAlias} = require('ln-sync');
|
|
5
|
+
const {returnResult} = require('asyncjs-util');
|
|
6
|
+
|
|
7
|
+
const {getGroupDetails} = require('./p2p');
|
|
8
|
+
|
|
9
|
+
const coordinatorFromJoinCode = n => n.slice(0, 66);
|
|
10
|
+
const groupIdFromJoinCode = n => n.slice(66);
|
|
11
|
+
const isCode = n => !!n && n.length === 98;
|
|
12
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
13
|
+
const niceName = n => `${n.alias} ${n.id}`.trim();
|
|
14
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
15
|
+
|
|
16
|
+
/** Ask to confirm joining a group
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
ask: <Ask Function>
|
|
20
|
+
lnd: <Authenticated LND API Object>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@returns via cbk or Promise
|
|
24
|
+
{
|
|
25
|
+
capacity: <Channel Capacity Tokens Number>
|
|
26
|
+
coordinator: <Group Coordinator Identity Public Key Hex String>
|
|
27
|
+
count: <Group Members Count>
|
|
28
|
+
id: <Group Id Hex String>
|
|
29
|
+
rate: <Chain Fee Tokens Per VByte Number>
|
|
30
|
+
}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = ({ask, lnd}, cbk) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
return asyncAuto({
|
|
35
|
+
// Check arguments
|
|
36
|
+
validate: cbk => {
|
|
37
|
+
if (!ask) {
|
|
38
|
+
return cbk([400, 'ExpectedAskFunctionToConfirmGroupJoin']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!lnd) {
|
|
42
|
+
return cbk([400, 'ExpectedAuthenticatedLndToConfirmGroupJoin']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return cbk();
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Ask for the group entry code
|
|
49
|
+
askForCode: ['validate', ({}, cbk) => {
|
|
50
|
+
return ask({
|
|
51
|
+
name: 'code',
|
|
52
|
+
message: 'Enter a group join code to join a group',
|
|
53
|
+
validate: input => !!isCode(input),
|
|
54
|
+
},
|
|
55
|
+
({code}) => cbk(null, code));
|
|
56
|
+
}],
|
|
57
|
+
|
|
58
|
+
// Get the wallet balance to make sure there are enough funds to join
|
|
59
|
+
getBalance: ['validate', ({}, cbk) => getChainBalance({lnd}, cbk)],
|
|
60
|
+
|
|
61
|
+
// Parse the group join code
|
|
62
|
+
group: ['askForCode', ({askForCode}, cbk) => {
|
|
63
|
+
const coordinator = coordinatorFromJoinCode(askForCode);
|
|
64
|
+
|
|
65
|
+
if (!isPublicKey(coordinator)) {
|
|
66
|
+
return cbk([400, 'ExpectedValidGroupJoinCodeToRequestGroupDetails']);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const id = groupIdFromJoinCode(askForCode);
|
|
70
|
+
|
|
71
|
+
return cbk(null, {id, coordinator})
|
|
72
|
+
}],
|
|
73
|
+
|
|
74
|
+
// Connect to the coordinator
|
|
75
|
+
connect: ['group', ({group}, cbk) => {
|
|
76
|
+
return connectPeer({lnd, id: group.coordinator}, cbk);
|
|
77
|
+
}],
|
|
78
|
+
|
|
79
|
+
// Get the coordinator node alias
|
|
80
|
+
getAlias: ['group', ({group}, cbk) => {
|
|
81
|
+
return getNodeAlias({lnd, id: group.coordinator}, cbk);
|
|
82
|
+
}],
|
|
83
|
+
|
|
84
|
+
// Get the group details from the coordinator
|
|
85
|
+
getDetails: ['group', ({group}, cbk) => {
|
|
86
|
+
return getGroupDetails({
|
|
87
|
+
lnd,
|
|
88
|
+
coordinator: group.coordinator,
|
|
89
|
+
id: group.id,
|
|
90
|
+
},
|
|
91
|
+
cbk);
|
|
92
|
+
}],
|
|
93
|
+
|
|
94
|
+
// Confirm the group join
|
|
95
|
+
ok: [
|
|
96
|
+
'getAlias',
|
|
97
|
+
'getBalance',
|
|
98
|
+
'getDetails',
|
|
99
|
+
({getAlias, getBalance, getDetails}, cbk) =>
|
|
100
|
+
{
|
|
101
|
+
// Check to make sure that there are on chain funds for this group
|
|
102
|
+
if (getBalance.chain_balance < getDetails.capacity) {
|
|
103
|
+
return cbk([
|
|
104
|
+
400,
|
|
105
|
+
'InsufficientChainFundsAvailableToJoinGroup',
|
|
106
|
+
{channel_capacity: tokensAsBigUnit(getDetails.capacity)},
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const coordinatedBy = `coordinated by ${niceName(getAlias)}`;
|
|
111
|
+
const members = `${getDetails.count} member group`;
|
|
112
|
+
const size = `with ${tokensAsBigUnit(getDetails.capacity)} channels`;
|
|
113
|
+
const rate = `${getDetails.rate}/vbyte chain fee`;
|
|
114
|
+
|
|
115
|
+
return ask({
|
|
116
|
+
name: 'join',
|
|
117
|
+
message: `Join ${members} ${size} at ${rate}, ${coordinatedBy}?`,
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
},
|
|
120
|
+
({join}) => cbk(null, join));
|
|
121
|
+
}],
|
|
122
|
+
|
|
123
|
+
// Go ahead with the group join
|
|
124
|
+
join: ['getDetails', 'group', 'ok', ({getDetails, group, ok}, cbk) => {
|
|
125
|
+
if (!ok) {
|
|
126
|
+
return cbk([400, 'CanceledGroupChannelJoin']);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return cbk(null, {
|
|
130
|
+
capacity: getDetails.capacity,
|
|
131
|
+
coordinator: group.coordinator,
|
|
132
|
+
count: getDetails.count,
|
|
133
|
+
id: group.id,
|
|
134
|
+
rate: getDetails.rate,
|
|
135
|
+
});
|
|
136
|
+
}],
|
|
137
|
+
},
|
|
138
|
+
returnResult({reject, resolve, of: 'join'}, cbk));
|
|
139
|
+
});
|
|
140
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
const {createPsbt} = require('psbt');
|
|
4
|
+
const {extendPsbt} = require('psbt');
|
|
5
|
+
const tinysecp = require('tiny-secp256k1');
|
|
6
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
7
|
+
|
|
8
|
+
const {ceil} = Math;
|
|
9
|
+
const dummyEcdsaSignature = Buffer.alloc(74);
|
|
10
|
+
const dummyPublicKey = Buffer.alloc(33);
|
|
11
|
+
const dummySchnorrSignature = Buffer.alloc(64);
|
|
12
|
+
const dustValue = 330;
|
|
13
|
+
const flatten = arr => [].concat(...arr);
|
|
14
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
15
|
+
const {isArray} = Array;
|
|
16
|
+
const isP2tr = n => n.startsWith('5120') && n.length === 68;
|
|
17
|
+
const isP2wpkh = n => n.startsWith('0014') && n.length === 44;
|
|
18
|
+
const {random} = Math;
|
|
19
|
+
const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
|
|
20
|
+
|
|
21
|
+
/** Assemble group channel unsigned PSBT
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
capacity: <Channel Capacity Tokens Number>
|
|
25
|
+
proposed: [{
|
|
26
|
+
[change]: <Change Output Hex String>
|
|
27
|
+
funding: <Funding Output Hex String>
|
|
28
|
+
utxos: [{
|
|
29
|
+
[non_witness_utxo]: <Spending Transaction Hex String>
|
|
30
|
+
transaction_id: <Transaction Id Hex String>
|
|
31
|
+
transaction_vout: <Transaction Output Index Number>
|
|
32
|
+
witness_utxo: {
|
|
33
|
+
script_pub: <Witness Output Script Hex String>
|
|
34
|
+
tokens: <Tokens Number>
|
|
35
|
+
}
|
|
36
|
+
}]
|
|
37
|
+
}]
|
|
38
|
+
rate: <Fee Rate Number>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@returns via cbk or Promise
|
|
42
|
+
{
|
|
43
|
+
psbt: <Unsigned Funding Transaction PSBT Hex String>
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
module.exports = ({capacity, proposed, rate}, cbk) => {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
return asyncAuto({
|
|
49
|
+
// Import ECPair library
|
|
50
|
+
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
51
|
+
|
|
52
|
+
// Check arguments
|
|
53
|
+
validate: cbk => {
|
|
54
|
+
if (!capacity) {
|
|
55
|
+
return cbk([400, 'ExpectedCapacityToAssembleUnsignedPsbt']);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isArray(proposed)) {
|
|
59
|
+
return cbk([400, 'ExpectedChannelProposalsToAssembleUnsignedPsbt']);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!rate) {
|
|
63
|
+
return cbk([400, 'ExpectedChainFeeRateToAssembleUnsignedPsbt']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return cbk();
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Derive funding transaction outputs
|
|
70
|
+
outputs: ['validate', ({}, cbk) => {
|
|
71
|
+
// Create a dummy tx to use for looking at vsize contributions
|
|
72
|
+
const tx = new Transaction();
|
|
73
|
+
|
|
74
|
+
// A tx has some base wrapper vbytes to pay for
|
|
75
|
+
const startSize = tx.virtualSize();
|
|
76
|
+
|
|
77
|
+
// Members should split the cost of the wrapper bytes
|
|
78
|
+
const wrapperShare = ceil(startSize / proposed.length);
|
|
79
|
+
|
|
80
|
+
const outputs = proposed.map(member => {
|
|
81
|
+
const tare = tx.virtualSize();
|
|
82
|
+
|
|
83
|
+
const inputsOffset = tx.ins.length;
|
|
84
|
+
|
|
85
|
+
[member.change, member.funding].filter(n => !!n).forEach(out => {
|
|
86
|
+
return tx.addOutput(hexAsBuffer(out), capacity);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
member.utxos.forEach(utxo => {
|
|
90
|
+
return tx.addInput(
|
|
91
|
+
hexAsBuffer(utxo.transaction_id),
|
|
92
|
+
utxo.transaction_vout
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
member.utxos.forEach((utxo, i) => {
|
|
97
|
+
// Set a dummy signature stack on the input
|
|
98
|
+
if (isP2wpkh(utxo.witness_utxo.script_pub)) {
|
|
99
|
+
return tx.setWitness(
|
|
100
|
+
i + inputsOffset,
|
|
101
|
+
[dummyPublicKey, dummyEcdsaSignature]
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (isP2tr(utxo.witness_utxo.script_pub)) {
|
|
106
|
+
return tx.setWitness(i + inputsOffset, [dummySchnorrSignature]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error('UnsupportedOutputType');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const vbytes = tx.virtualSize() - tare + wrapperShare;
|
|
113
|
+
|
|
114
|
+
const funded = sumOf(member.utxos.map(n => n.witness_utxo.tokens));
|
|
115
|
+
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
script: member.funding,
|
|
119
|
+
tokens: capacity,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
script: member.change,
|
|
123
|
+
tokens: funded - capacity - (vbytes * rate),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Collect outputs and shuffle them
|
|
129
|
+
const finalOutputs = flatten(outputs).filter(n => !!n.script)
|
|
130
|
+
.map(value => ({value, sort: random()}))
|
|
131
|
+
.sort((a, b) => a.sort - b.sort)
|
|
132
|
+
.map(({value}) => value);
|
|
133
|
+
|
|
134
|
+
return cbk(null, finalOutputs);
|
|
135
|
+
}],
|
|
136
|
+
|
|
137
|
+
// Assemble the funding for the group channel
|
|
138
|
+
funding: ['ecp', 'outputs', ({ecp, outputs}, cbk) => {
|
|
139
|
+
// Put together all inputs funding the transaction, shuffle inputs
|
|
140
|
+
const utxos = flatten(proposed.map(n => n.utxos))
|
|
141
|
+
.map(utxo => ({
|
|
142
|
+
id: utxo.transaction_id,
|
|
143
|
+
non_witness_utxo: utxo.non_witness_utxo,
|
|
144
|
+
vout: utxo.transaction_vout,
|
|
145
|
+
witness_utxo: utxo.witness_utxo,
|
|
146
|
+
}))
|
|
147
|
+
.map(value => ({value, sort: random()}))
|
|
148
|
+
.sort((a, b) => a.sort - b.sort)
|
|
149
|
+
.map(({value}) => value);
|
|
150
|
+
|
|
151
|
+
// Setup a baseline PSBT with the inputs and outputs
|
|
152
|
+
const fundingBase = createPsbt({outputs, utxos});
|
|
153
|
+
|
|
154
|
+
// Extend the base PSBT with the UTXO metadata
|
|
155
|
+
const extended = extendPsbt({
|
|
156
|
+
ecp,
|
|
157
|
+
inputs: utxos.map(utxo => ({
|
|
158
|
+
non_witness_utxo: utxo.non_witness_utxo,
|
|
159
|
+
witness_utxo: utxo.witness_utxo,
|
|
160
|
+
})),
|
|
161
|
+
psbt: fundingBase.psbt,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return cbk(null, {psbt: extended.psbt});
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
167
|
+
returnResult({reject, resolve, of: 'funding'}, cbk));
|
|
168
|
+
});
|
|
169
|
+
};
|