mainnet-js 0.4.34 → 0.4.35
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/dist/index.html +1 -1
- package/dist/main/transaction/Wif.d.ts +6 -4
- package/dist/main/transaction/Wif.js +14 -7
- package/dist/main/transaction/Wif.js.map +1 -1
- package/dist/main/transaction/allocateFee.d.ts +7 -0
- package/dist/main/transaction/allocateFee.js +118 -0
- package/dist/main/transaction/allocateFee.js.map +1 -0
- package/dist/main/wallet/Slp.js +4 -1
- package/dist/main/wallet/Slp.js.map +1 -1
- package/dist/main/wallet/Wif.js +20 -3
- package/dist/main/wallet/Wif.js.map +1 -1
- package/dist/main/wallet/enum.d.ts +9 -0
- package/dist/main/wallet/enum.js +11 -1
- package/dist/main/wallet/enum.js.map +1 -1
- package/dist/main/wallet/interface.d.ts +2 -1
- package/dist/mainnet-0.4.35.js +2 -0
- package/dist/{mainnet-0.4.34.js.LICENSE.txt → mainnet-0.4.35.js.LICENSE.txt} +0 -0
- package/dist/module/transaction/Wif.d.ts +6 -4
- package/dist/module/transaction/Wif.js +14 -7
- package/dist/module/transaction/Wif.js.map +1 -1
- package/dist/module/transaction/allocateFee.d.ts +7 -0
- package/dist/module/transaction/allocateFee.js +110 -0
- package/dist/module/transaction/allocateFee.js.map +1 -0
- package/dist/module/wallet/Slp.js +4 -1
- package/dist/module/wallet/Slp.js.map +1 -1
- package/dist/module/wallet/Wif.js +21 -4
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/module/wallet/enum.d.ts +9 -0
- package/dist/module/wallet/enum.js +10 -0
- package/dist/module/wallet/enum.js.map +1 -1
- package/dist/module/wallet/interface.d.ts +2 -1
- package/dist/tsconfig.browser.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/transaction/Wif.ts +24 -7
- package/src/transaction/allocateFee.test.ts +309 -0
- package/src/transaction/allocateFee.ts +132 -0
- package/src/wallet/Slp.ts +5 -1
- package/src/wallet/Wif.ts +29 -4
- package/src/wallet/enum.ts +10 -0
- package/src/wallet/interface.ts +2 -1
- package/dist/mainnet-0.4.34.js +0 -2
package/package.json
CHANGED
package/src/transaction/Wif.ts
CHANGED
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
AuthenticationProgramStateBCH,
|
|
14
14
|
} from "@bitauth/libauth";
|
|
15
15
|
import { UtxoI } from "../interface";
|
|
16
|
+
import { allocateFee } from "./allocateFee";
|
|
16
17
|
|
|
17
18
|
import { DUST_UTXO_THRESHOLD } from "../constant";
|
|
18
19
|
import { OpReturnData, SendRequest } from "../wallet/model";
|
|
19
20
|
import { amountInSatoshi } from "../util/amountInSatoshi";
|
|
20
21
|
import { sumSendRequestAmounts } from "../util/sumSendRequestAmounts";
|
|
21
22
|
import { sumUtxoValue } from "../util/sumUtxoValue";
|
|
23
|
+
import { FeePaidByEnum } from "../wallet/enum";
|
|
22
24
|
|
|
23
25
|
// Build a transaction for a p2pkh transaction for a non HD wallet
|
|
24
26
|
export async function buildP2pkhNonHdTransaction(
|
|
@@ -27,7 +29,8 @@ export async function buildP2pkhNonHdTransaction(
|
|
|
27
29
|
signingKey: Uint8Array,
|
|
28
30
|
fee: number = 0,
|
|
29
31
|
discardChange = false,
|
|
30
|
-
slpOutputs: any[] = []
|
|
32
|
+
slpOutputs: any[] = [],
|
|
33
|
+
feePaidBy: FeePaidByEnum = FeePaidByEnum.change
|
|
31
34
|
) {
|
|
32
35
|
if (!signingKey) {
|
|
33
36
|
throw new Error("Missing signing key when building transaction");
|
|
@@ -42,6 +45,7 @@ export async function buildP2pkhNonHdTransaction(
|
|
|
42
45
|
|
|
43
46
|
const compiler = await authenticationTemplateToCompilerBCH(template);
|
|
44
47
|
const inputAmount = await sumUtxoValue(inputs);
|
|
48
|
+
|
|
45
49
|
const sendAmount = await sumSendRequestAmounts(outputs);
|
|
46
50
|
|
|
47
51
|
// Get the change locking bytecode
|
|
@@ -53,11 +57,13 @@ export async function buildP2pkhNonHdTransaction(
|
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
try {
|
|
60
|
+
const changeAmount = BigInt(inputAmount) - BigInt(sendAmount) - BigInt(fee);
|
|
61
|
+
|
|
62
|
+
outputs = allocateFee(outputs, fee, feePaidBy, changeAmount);
|
|
63
|
+
|
|
56
64
|
let lockedOutputs = await prepareOutputs(outputs);
|
|
57
65
|
|
|
58
66
|
if (discardChange !== true) {
|
|
59
|
-
const changeAmount =
|
|
60
|
-
BigInt(inputAmount) - BigInt(sendAmount) - BigInt(fee);
|
|
61
67
|
if (changeAmount > DUST_UTXO_THRESHOLD) {
|
|
62
68
|
lockedOutputs.push({
|
|
63
69
|
lockingBytecode: changeLockingBytecode.bytecode,
|
|
@@ -188,7 +194,8 @@ export function prepareOpReturnOutput(request: OpReturnData) {
|
|
|
188
194
|
export async function getSuitableUtxos(
|
|
189
195
|
unspentOutputs: UtxoI[],
|
|
190
196
|
amountRequired: BigInt | undefined,
|
|
191
|
-
bestHeight: number
|
|
197
|
+
bestHeight: number,
|
|
198
|
+
feePaidBy: FeePaidByEnum
|
|
192
199
|
): Promise<UtxoI[]> {
|
|
193
200
|
let suitableUtxos: UtxoI[] = [];
|
|
194
201
|
let amountAvailable = BigInt(0);
|
|
@@ -209,6 +216,11 @@ export async function getSuitableUtxos(
|
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
|
|
219
|
+
// if the fee is split with a feePaidBy option, skip checking change.
|
|
220
|
+
if (feePaidBy && feePaidBy != FeePaidByEnum.change) {
|
|
221
|
+
return suitableUtxos;
|
|
222
|
+
}
|
|
223
|
+
|
|
212
224
|
// If the amount needed is met, or no amount is given, return
|
|
213
225
|
if (typeof amountRequired === "undefined") {
|
|
214
226
|
return suitableUtxos;
|
|
@@ -232,12 +244,14 @@ export async function getFeeAmount({
|
|
|
232
244
|
privateKey,
|
|
233
245
|
relayFeePerByteInSatoshi,
|
|
234
246
|
slpOutputs,
|
|
247
|
+
feePaidBy,
|
|
235
248
|
}: {
|
|
236
249
|
utxos: UtxoI[];
|
|
237
250
|
sendRequests: Array<SendRequest | OpReturnData>;
|
|
238
251
|
privateKey: Uint8Array;
|
|
239
252
|
relayFeePerByteInSatoshi: number;
|
|
240
253
|
slpOutputs: any[];
|
|
254
|
+
feePaidBy: FeePaidByEnum;
|
|
241
255
|
}) {
|
|
242
256
|
// build transaction
|
|
243
257
|
if (utxos) {
|
|
@@ -248,7 +262,8 @@ export async function getFeeAmount({
|
|
|
248
262
|
privateKey,
|
|
249
263
|
0, //DUST_UTXO_THRESHOLD
|
|
250
264
|
false,
|
|
251
|
-
slpOutputs
|
|
265
|
+
slpOutputs,
|
|
266
|
+
feePaidBy
|
|
252
267
|
);
|
|
253
268
|
|
|
254
269
|
return draftTransaction.length * relayFeePerByteInSatoshi + 1;
|
|
@@ -266,7 +281,8 @@ export async function buildEncodedTransaction(
|
|
|
266
281
|
privateKey: Uint8Array,
|
|
267
282
|
fee: number = 0,
|
|
268
283
|
discardChange = false,
|
|
269
|
-
slpOutputs: any[] = []
|
|
284
|
+
slpOutputs: any[] = [],
|
|
285
|
+
feePaidBy: FeePaidByEnum = FeePaidByEnum.change
|
|
270
286
|
) {
|
|
271
287
|
let txn = await buildP2pkhNonHdTransaction(
|
|
272
288
|
fundingUtxos,
|
|
@@ -274,7 +290,8 @@ export async function buildEncodedTransaction(
|
|
|
274
290
|
privateKey,
|
|
275
291
|
fee,
|
|
276
292
|
discardChange,
|
|
277
|
-
slpOutputs
|
|
293
|
+
slpOutputs,
|
|
294
|
+
feePaidBy
|
|
278
295
|
);
|
|
279
296
|
// submit transaction
|
|
280
297
|
if (txn.success) {
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { allocateFee, sortSendRequests } from "./allocateFee";
|
|
2
|
+
import { FeePaidByEnum } from "../wallet/enum";
|
|
3
|
+
import { asSendRequestObject } from "../util/asSendRequestObject";
|
|
4
|
+
import { SendRequest } from "..";
|
|
5
|
+
import { RegTestWallet } from "../wallet/Wif";
|
|
6
|
+
|
|
7
|
+
test("Should get the regtest wallet balance", async () => {
|
|
8
|
+
// Build Alice's wallet from Wallet Import Format string, send some sats
|
|
9
|
+
if (!process.env.ADDRESS) {
|
|
10
|
+
throw Error("Attempted to pass an empty address");
|
|
11
|
+
} else {
|
|
12
|
+
const funder = await RegTestWallet.fromId(
|
|
13
|
+
`wif:regtest:${process.env.PRIVATE_WIF!}`
|
|
14
|
+
);
|
|
15
|
+
const alice = await RegTestWallet.newRandom();
|
|
16
|
+
const bob = await RegTestWallet.newRandom();
|
|
17
|
+
const charlie = await RegTestWallet.newRandom();
|
|
18
|
+
const dave = await RegTestWallet.newRandom();
|
|
19
|
+
const edward = await RegTestWallet.newRandom();
|
|
20
|
+
await funder.send([
|
|
21
|
+
{
|
|
22
|
+
cashaddr: alice.cashaddr!,
|
|
23
|
+
value: 4500,
|
|
24
|
+
unit: "satoshis",
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
await alice.send(
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
cashaddr: bob.cashaddr!,
|
|
31
|
+
unit: "sat",
|
|
32
|
+
value: 549,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
cashaddr: charlie.cashaddr!,
|
|
36
|
+
unit: "sat",
|
|
37
|
+
value: 550,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
cashaddr: dave.cashaddr!,
|
|
41
|
+
unit: "sat",
|
|
42
|
+
value: 551,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
cashaddr: edward.cashaddr!,
|
|
46
|
+
unit: "sat",
|
|
47
|
+
value: 2552,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
{ feePaidBy: FeePaidByEnum.changeThenAny }
|
|
51
|
+
);
|
|
52
|
+
expect(await alice.getBalance("sat")).toBe(0);
|
|
53
|
+
expect(await bob.getBalance("sat")).toBe(0);
|
|
54
|
+
expect(await charlie.getBalance("sat")).toBe(550);
|
|
55
|
+
expect(await dave.getBalance("sat")).toBe(551);
|
|
56
|
+
expect(await edward.getBalance("sat")).toBe(2552);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("Expect first in to subtract fee from first", async () => {
|
|
61
|
+
let to = [
|
|
62
|
+
["alice", 2000, "sat"],
|
|
63
|
+
["bob", 2000, "sat"],
|
|
64
|
+
];
|
|
65
|
+
let fee = 1;
|
|
66
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
67
|
+
let allocatedInputs = await allocateFee(
|
|
68
|
+
requests,
|
|
69
|
+
fee,
|
|
70
|
+
FeePaidByEnum.first,
|
|
71
|
+
BigInt(0)
|
|
72
|
+
);
|
|
73
|
+
expect(allocatedInputs[0].value).toBeLessThan(2000);
|
|
74
|
+
expect(allocatedInputs[1].value).toBe(2000);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("Expect last to subtract fee from last", async () => {
|
|
78
|
+
let to = [
|
|
79
|
+
["alice", 2000, "sat"],
|
|
80
|
+
["bob", 2000, "sat"],
|
|
81
|
+
];
|
|
82
|
+
let fee = 1;
|
|
83
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
84
|
+
let allocatedInputs = allocateFee(
|
|
85
|
+
requests,
|
|
86
|
+
fee,
|
|
87
|
+
FeePaidByEnum.last,
|
|
88
|
+
BigInt(0)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(allocatedInputs[0].value).toBe(2000);
|
|
92
|
+
expect(allocatedInputs[1].value).toBeLessThan(2000);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("Expect all to allocate fees equally", async () => {
|
|
96
|
+
let to = [
|
|
97
|
+
["alice", 2000, "sat"],
|
|
98
|
+
["bob", 2000, "sat"],
|
|
99
|
+
["charlie", 2000, "sat"],
|
|
100
|
+
];
|
|
101
|
+
let fee = 3;
|
|
102
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
103
|
+
|
|
104
|
+
let allocatedInputs = allocateFee(
|
|
105
|
+
requests,
|
|
106
|
+
fee,
|
|
107
|
+
FeePaidByEnum.any,
|
|
108
|
+
BigInt(0)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(allocatedInputs[0].value).toBe(1999);
|
|
112
|
+
expect(allocatedInputs[1].value).toBe(1999);
|
|
113
|
+
expect(allocatedInputs[2].value).toBe(1999);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("Expect all to allocate fees equally, taking dust result", async () => {
|
|
117
|
+
let to = [
|
|
118
|
+
["alice", 2000, "sat"],
|
|
119
|
+
["bob", 547, "sat"],
|
|
120
|
+
["charlie", 2000, "sat"],
|
|
121
|
+
];
|
|
122
|
+
let fee = 300;
|
|
123
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
124
|
+
|
|
125
|
+
let allocatedInputs = allocateFee(
|
|
126
|
+
requests,
|
|
127
|
+
fee,
|
|
128
|
+
FeePaidByEnum.any,
|
|
129
|
+
BigInt(0)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(allocatedInputs[0].value).toBe(2000);
|
|
133
|
+
expect(allocatedInputs[1].value).toBe(2000);
|
|
134
|
+
expect(allocatedInputs.length).toBe(2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("Expect all to allocate fees equally, taking dust result output, dividing remainder", async () => {
|
|
138
|
+
let to = [
|
|
139
|
+
["alice", 2000, "sat"],
|
|
140
|
+
["bob", 547, "sat"],
|
|
141
|
+
["charlie", 2000, "sat"],
|
|
142
|
+
];
|
|
143
|
+
let fee = 647;
|
|
144
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
145
|
+
|
|
146
|
+
let allocatedInputs = allocateFee(
|
|
147
|
+
requests,
|
|
148
|
+
fee,
|
|
149
|
+
FeePaidByEnum.any,
|
|
150
|
+
BigInt(0)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(allocatedInputs[0].value).toBe(1950);
|
|
154
|
+
expect(allocatedInputs[1].value).toBe(1950);
|
|
155
|
+
expect(allocatedInputs.length).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("Expect an odd fee to be applied to have remainder applied to first receipt", async () => {
|
|
159
|
+
let to = [
|
|
160
|
+
["alice", 2000, "sat"],
|
|
161
|
+
["bob", 2000, "sat"],
|
|
162
|
+
["charlie", 2000, "sat"],
|
|
163
|
+
];
|
|
164
|
+
let fee = 301;
|
|
165
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
166
|
+
|
|
167
|
+
let allocatedInputs = allocateFee(
|
|
168
|
+
requests,
|
|
169
|
+
fee,
|
|
170
|
+
FeePaidByEnum.any,
|
|
171
|
+
BigInt(0)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(allocatedInputs[0].value).toBe(1899);
|
|
175
|
+
expect(allocatedInputs[1].value).toBe(1900);
|
|
176
|
+
expect(allocatedInputs[2].value).toBe(1900);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("Expect insufficient funds to error", async () => {
|
|
180
|
+
expect.assertions(1);
|
|
181
|
+
try {
|
|
182
|
+
let to = [
|
|
183
|
+
["alice", 2000, "sat"],
|
|
184
|
+
["bob", 2000, "sat"],
|
|
185
|
+
["charlie", 2000, "sat"],
|
|
186
|
+
];
|
|
187
|
+
let fee = 7000;
|
|
188
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
189
|
+
let allocatedInputs = allocateFee(
|
|
190
|
+
requests,
|
|
191
|
+
fee,
|
|
192
|
+
FeePaidByEnum.changeThenAny,
|
|
193
|
+
BigInt(999)
|
|
194
|
+
);
|
|
195
|
+
} catch (e: any) {
|
|
196
|
+
expect(e.message).toBe("Insufficient funds for transaction given fee");
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("Expect dust amounts to error", async () => {
|
|
201
|
+
expect.assertions(1);
|
|
202
|
+
try {
|
|
203
|
+
let to = [
|
|
204
|
+
["alice", 2000, "sat"],
|
|
205
|
+
["bob", 2000, "sat"],
|
|
206
|
+
["charlie", 2000, "sat"],
|
|
207
|
+
];
|
|
208
|
+
let fee = 1500;
|
|
209
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
210
|
+
let allocatedInputs = allocateFee(
|
|
211
|
+
requests,
|
|
212
|
+
fee,
|
|
213
|
+
FeePaidByEnum.first,
|
|
214
|
+
BigInt(0)
|
|
215
|
+
);
|
|
216
|
+
} catch (e: any) {
|
|
217
|
+
expect(e.message).toBe("Fee strategy would result in dust output");
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("Expect near-dust amounts not to error", async () => {
|
|
222
|
+
let to = [
|
|
223
|
+
["alice", 1000, "sat"],
|
|
224
|
+
["bob", 1000, "sat"],
|
|
225
|
+
["charlie", 1000, "sat"],
|
|
226
|
+
];
|
|
227
|
+
let fee = 1362;
|
|
228
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
229
|
+
let result = allocateFee(requests, fee, FeePaidByEnum.any, BigInt(0));
|
|
230
|
+
expect(result[0].value).toBe(546);
|
|
231
|
+
expect(result[1].value).toBe(546);
|
|
232
|
+
expect(result[2].value).toBe(546);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("Expect `any` to not consume change", async () => {
|
|
236
|
+
let to = [
|
|
237
|
+
["alice", 1000, "sat"],
|
|
238
|
+
["bob", 1000, "sat"],
|
|
239
|
+
["charlie", 1000, "sat"],
|
|
240
|
+
];
|
|
241
|
+
let fee = 1362;
|
|
242
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
243
|
+
let result = allocateFee(requests, fee, FeePaidByEnum.any, BigInt(1362));
|
|
244
|
+
expect(result[0].value).toBe(546);
|
|
245
|
+
expect(result[1].value).toBe(546);
|
|
246
|
+
expect(result[2].value).toBe(546);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("Expect `change,any` to consume only change", async () => {
|
|
250
|
+
let to = [
|
|
251
|
+
["alice", 1000, "sat"],
|
|
252
|
+
["bob", 1000, "sat"],
|
|
253
|
+
["charlie", 1000, "sat"],
|
|
254
|
+
];
|
|
255
|
+
let fee = 1362;
|
|
256
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
257
|
+
let result = allocateFee(
|
|
258
|
+
requests,
|
|
259
|
+
fee,
|
|
260
|
+
FeePaidByEnum.changeThenAny,
|
|
261
|
+
BigInt(1362)
|
|
262
|
+
);
|
|
263
|
+
expect(result[0].value).toBe(1000);
|
|
264
|
+
expect(result[1].value).toBe(1000);
|
|
265
|
+
expect(result[2].value).toBe(1000);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("Expect `change,any` to use both", async () => {
|
|
269
|
+
let to = [
|
|
270
|
+
["alice", 1000, "sat"],
|
|
271
|
+
["bob", 1000, "sat"],
|
|
272
|
+
["charlie", 1000, "sat"],
|
|
273
|
+
];
|
|
274
|
+
let fee = 1362 * 2;
|
|
275
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
276
|
+
let result = allocateFee(
|
|
277
|
+
requests,
|
|
278
|
+
fee,
|
|
279
|
+
FeePaidByEnum.changeThenAny,
|
|
280
|
+
BigInt(1362)
|
|
281
|
+
);
|
|
282
|
+
expect(result[0].value).toBe(546);
|
|
283
|
+
expect(result[1].value).toBe(546);
|
|
284
|
+
expect(result[2].value).toBe(546);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("Expect sortSendRequests to sort by lowest value first", async () => {
|
|
288
|
+
let to = [
|
|
289
|
+
["alice", 2000, "sat"],
|
|
290
|
+
["bob", 547, "sat"],
|
|
291
|
+
["charlie", 1, "sat"],
|
|
292
|
+
["dave", 4, "sat"],
|
|
293
|
+
["edward", 6, "sat"],
|
|
294
|
+
["fred", 2000, "sat"],
|
|
295
|
+
["greg", 2000, "sat"],
|
|
296
|
+
["harry", 2000, "sat"],
|
|
297
|
+
];
|
|
298
|
+
let fee = 1;
|
|
299
|
+
let requests = asSendRequestObject(to) as SendRequest[];
|
|
300
|
+
|
|
301
|
+
let result = sortSendRequests(requests);
|
|
302
|
+
expect(result[0].value).toBe(1);
|
|
303
|
+
expect(result[1].value).toBe(4);
|
|
304
|
+
expect(result[2].value).toBe(6);
|
|
305
|
+
expect(result[3].value).toBe(547);
|
|
306
|
+
expect(result[4].value).toBe(2000);
|
|
307
|
+
expect(result[5].value).toBe(2000);
|
|
308
|
+
expect(result.length).toBe(8);
|
|
309
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { OpReturnData, SendRequest } from "../wallet/model";
|
|
2
|
+
import { FeePaidByEnum } from "../wallet/enum";
|
|
3
|
+
import { DUST_UTXO_THRESHOLD } from "../constant";
|
|
4
|
+
|
|
5
|
+
export function checkFeeForDust(value: number) {
|
|
6
|
+
if (value < DUST_UTXO_THRESHOLD) {
|
|
7
|
+
throw Error("Fee strategy would result in dust output");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function checkSatsAvailable(
|
|
12
|
+
sendRequestArray: Array<SendRequest>,
|
|
13
|
+
fee: number
|
|
14
|
+
) {
|
|
15
|
+
let amountAvailable = sendRequestArray.reduce(function (sum, r) {
|
|
16
|
+
return sum + (r.value - DUST_UTXO_THRESHOLD);
|
|
17
|
+
}, 0);
|
|
18
|
+
if (amountAvailable < fee) {
|
|
19
|
+
throw Error("Insufficient funds for transaction given fee");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function checkForOpReturn(
|
|
24
|
+
output: SendRequest | OpReturnData
|
|
25
|
+
): SendRequest {
|
|
26
|
+
if (output instanceof OpReturnData) {
|
|
27
|
+
throw Error("Cannot specify fee to be paid by OpReturnData");
|
|
28
|
+
}
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function sortSendRequests(sendRequestArray: Array<SendRequest>) {
|
|
33
|
+
return sendRequestArray.sort(
|
|
34
|
+
(a: SendRequest, b: SendRequest) => a.value - b.value
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function distributeFees(requests: Array<SendRequest>, fee: number) {
|
|
39
|
+
checkSatsAvailable(requests, fee);
|
|
40
|
+
fee = Number(fee);
|
|
41
|
+
for (let r = 0; r < requests.length; r++) {
|
|
42
|
+
if (fee > 0) {
|
|
43
|
+
checkForOpReturn(requests[r]);
|
|
44
|
+
let perRequestFee = Math.floor(fee / (requests.length - r));
|
|
45
|
+
perRequestFee += fee % (requests.length - r);
|
|
46
|
+
if (requests[r].value - perRequestFee < DUST_UTXO_THRESHOLD) {
|
|
47
|
+
fee -= requests[r].value;
|
|
48
|
+
requests[r].value = 0;
|
|
49
|
+
} else {
|
|
50
|
+
fee -= perRequestFee;
|
|
51
|
+
requests[r].value -= perRequestFee;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return requests.filter((r) => r.value >= DUST_UTXO_THRESHOLD);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function firstPays(requests: Array<SendRequest | OpReturnData>, fee: number) {
|
|
59
|
+
let payer = requests.shift()!;
|
|
60
|
+
payer = checkForOpReturn(payer);
|
|
61
|
+
payer.value = payer.value! - fee;
|
|
62
|
+
checkFeeForDust(payer.value);
|
|
63
|
+
requests.unshift(payer);
|
|
64
|
+
return requests;
|
|
65
|
+
}
|
|
66
|
+
function lastPays(requests: Array<SendRequest | OpReturnData>, fee: number) {
|
|
67
|
+
let payer = requests.pop()!;
|
|
68
|
+
payer = checkForOpReturn(payer);
|
|
69
|
+
payer.value = payer.value! - fee;
|
|
70
|
+
checkFeeForDust(payer.value);
|
|
71
|
+
requests.push(payer);
|
|
72
|
+
return requests;
|
|
73
|
+
}
|
|
74
|
+
function anyPays(requests: Array<SendRequest | OpReturnData>, fee: number) {
|
|
75
|
+
for (let r of requests) {
|
|
76
|
+
checkForOpReturn(r);
|
|
77
|
+
}
|
|
78
|
+
requests = sortSendRequests(requests as Array<SendRequest>);
|
|
79
|
+
requests = distributeFees(requests as Array<SendRequest>, fee);
|
|
80
|
+
return requests;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function changeThenFallback(
|
|
84
|
+
requests: Array<SendRequest | OpReturnData>,
|
|
85
|
+
fee: number,
|
|
86
|
+
change: bigint,
|
|
87
|
+
fallbackFn: Function
|
|
88
|
+
) {
|
|
89
|
+
if (BigInt(fee) > change) {
|
|
90
|
+
let outstandingFee = BigInt(fee) - change;
|
|
91
|
+
requests = fallbackFn(requests, outstandingFee);
|
|
92
|
+
}
|
|
93
|
+
return requests;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function allocateFee(
|
|
97
|
+
requests: Array<SendRequest | OpReturnData>,
|
|
98
|
+
fee: number,
|
|
99
|
+
feePaidBy: FeePaidByEnum,
|
|
100
|
+
change: bigint
|
|
101
|
+
): Array<SendRequest> {
|
|
102
|
+
if (requests.length > 0) {
|
|
103
|
+
switch (feePaidBy) {
|
|
104
|
+
case FeePaidByEnum.change:
|
|
105
|
+
// handled by default
|
|
106
|
+
break;
|
|
107
|
+
case FeePaidByEnum.changeThenFirst:
|
|
108
|
+
requests = changeThenFallback(requests, fee, change, firstPays);
|
|
109
|
+
break;
|
|
110
|
+
case FeePaidByEnum.changeThenLast:
|
|
111
|
+
requests = changeThenFallback(requests, fee, change, lastPays);
|
|
112
|
+
break;
|
|
113
|
+
case FeePaidByEnum.changeThenAny:
|
|
114
|
+
requests = changeThenFallback(requests, fee, change, anyPays);
|
|
115
|
+
break;
|
|
116
|
+
case FeePaidByEnum.first:
|
|
117
|
+
requests = firstPays(requests, fee);
|
|
118
|
+
break;
|
|
119
|
+
case FeePaidByEnum.last:
|
|
120
|
+
requests = lastPays(requests, fee);
|
|
121
|
+
break;
|
|
122
|
+
case FeePaidByEnum.any:
|
|
123
|
+
requests = anyPays(requests, fee);
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
throw Error("FeePaidBy option not recognized");
|
|
127
|
+
}
|
|
128
|
+
return requests as Array<SendRequest>;
|
|
129
|
+
} else {
|
|
130
|
+
throw Error("Attempted to specify feePaidBy on zero length SendRequest");
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/wallet/Slp.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { GsppProvider } from "../slp/GsppProvider";
|
|
|
53
53
|
import { delay } from "../util/delay";
|
|
54
54
|
import { Util } from "./Util";
|
|
55
55
|
import { Mainnet } from "../index";
|
|
56
|
+
import { FeePaidByEnum } from "./enum";
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Class to manage an slp enabled wallet.
|
|
@@ -680,6 +681,7 @@ export class Slp {
|
|
|
680
681
|
privateKey: this.wallet.privateKey,
|
|
681
682
|
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
682
683
|
slpOutputs: slpOutputsResult.SlpOutputs,
|
|
684
|
+
feePaidBy: FeePaidByEnum.change,
|
|
683
685
|
});
|
|
684
686
|
|
|
685
687
|
const bchSpendAmount = slpOutputsResult.BchSendRequests.map(
|
|
@@ -689,7 +691,8 @@ export class Slp {
|
|
|
689
691
|
let fundingUtxos = await getSuitableUtxos(
|
|
690
692
|
fundingBchUtxos,
|
|
691
693
|
BigInt(bchSpendAmount) + BigInt(feeEstimate),
|
|
692
|
-
bestHeight
|
|
694
|
+
bestHeight,
|
|
695
|
+
FeePaidByEnum.change
|
|
693
696
|
);
|
|
694
697
|
|
|
695
698
|
if (fundingUtxos.length === 0) {
|
|
@@ -704,6 +707,7 @@ export class Slp {
|
|
|
704
707
|
privateKey: this.wallet.privateKey,
|
|
705
708
|
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
706
709
|
slpOutputs: slpOutputsResult.SlpOutputs,
|
|
710
|
+
feePaidBy: FeePaidByEnum.change,
|
|
707
711
|
});
|
|
708
712
|
|
|
709
713
|
const encodedTransaction = await buildEncodedTransaction(
|
package/src/wallet/Wif.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { networkPrefixMap } from "../enum";
|
|
|
31
31
|
import { PrivateKeyI, UtxoI } from "../interface";
|
|
32
32
|
|
|
33
33
|
import { BaseWallet } from "./Base";
|
|
34
|
-
import { WalletTypeEnum } from "./enum";
|
|
34
|
+
import { FeePaidByEnum, WalletTypeEnum } from "./enum";
|
|
35
35
|
import {
|
|
36
36
|
CancelWatchFn,
|
|
37
37
|
SendRequestOptionsI,
|
|
@@ -764,6 +764,13 @@ export class Wallet extends BaseWallet {
|
|
|
764
764
|
this._slpSemiAware = true;
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
let feePaidBy;
|
|
768
|
+
if (params.options && params.options.feePaidBy) {
|
|
769
|
+
feePaidBy = params.options.feePaidBy;
|
|
770
|
+
} else {
|
|
771
|
+
feePaidBy = FeePaidByEnum.change;
|
|
772
|
+
}
|
|
773
|
+
|
|
767
774
|
// get inputs
|
|
768
775
|
let utxos: UtxoI[];
|
|
769
776
|
if (params.options && params.options.utxoIds) {
|
|
@@ -787,7 +794,12 @@ export class Wallet extends BaseWallet {
|
|
|
787
794
|
.fill(0)
|
|
788
795
|
.map(() => sendRequest);
|
|
789
796
|
|
|
790
|
-
const fundingUtxos = await getSuitableUtxos(
|
|
797
|
+
const fundingUtxos = await getSuitableUtxos(
|
|
798
|
+
utxos,
|
|
799
|
+
undefined,
|
|
800
|
+
bestHeight,
|
|
801
|
+
feePaidBy
|
|
802
|
+
);
|
|
791
803
|
const relayFeePerByteInSatoshi = await getRelayFeeCache(this.provider!);
|
|
792
804
|
const fee = await getFeeAmount({
|
|
793
805
|
utxos: fundingUtxos,
|
|
@@ -795,6 +807,7 @@ export class Wallet extends BaseWallet {
|
|
|
795
807
|
privateKey: this.privateKey,
|
|
796
808
|
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
797
809
|
slpOutputs: [],
|
|
810
|
+
feePaidBy: feePaidBy,
|
|
798
811
|
});
|
|
799
812
|
const spendableAmount = await sumUtxoValue(fundingUtxos);
|
|
800
813
|
|
|
@@ -950,6 +963,13 @@ export class Wallet extends BaseWallet {
|
|
|
950
963
|
this._slpSemiAware = true;
|
|
951
964
|
}
|
|
952
965
|
|
|
966
|
+
let feePaidBy;
|
|
967
|
+
if (options && options.feePaidBy) {
|
|
968
|
+
feePaidBy = options.feePaidBy;
|
|
969
|
+
} else {
|
|
970
|
+
feePaidBy = FeePaidByEnum.change;
|
|
971
|
+
}
|
|
972
|
+
|
|
953
973
|
// get inputs from options or query all inputs
|
|
954
974
|
let utxos: UtxoI[];
|
|
955
975
|
if (options && options.utxoIds) {
|
|
@@ -977,12 +997,14 @@ export class Wallet extends BaseWallet {
|
|
|
977
997
|
privateKey: this.privateKey,
|
|
978
998
|
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
979
999
|
slpOutputs: [],
|
|
1000
|
+
feePaidBy: feePaidBy,
|
|
980
1001
|
});
|
|
981
1002
|
|
|
982
1003
|
const fundingUtxos = await getSuitableUtxos(
|
|
983
1004
|
utxos,
|
|
984
1005
|
BigInt(spendAmount) + BigInt(feeEstimate),
|
|
985
|
-
bestHeight
|
|
1006
|
+
bestHeight,
|
|
1007
|
+
feePaidBy
|
|
986
1008
|
);
|
|
987
1009
|
if (fundingUtxos.length === 0) {
|
|
988
1010
|
throw Error(
|
|
@@ -995,13 +1017,16 @@ export class Wallet extends BaseWallet {
|
|
|
995
1017
|
privateKey: this.privateKey,
|
|
996
1018
|
relayFeePerByteInSatoshi: relayFeePerByteInSatoshi,
|
|
997
1019
|
slpOutputs: [],
|
|
1020
|
+
feePaidBy: feePaidBy,
|
|
998
1021
|
});
|
|
999
1022
|
const encodedTransaction = await buildEncodedTransaction(
|
|
1000
1023
|
fundingUtxos,
|
|
1001
1024
|
sendRequests,
|
|
1002
1025
|
this.privateKey,
|
|
1003
1026
|
fee,
|
|
1004
|
-
discardChange
|
|
1027
|
+
discardChange,
|
|
1028
|
+
[],
|
|
1029
|
+
feePaidBy
|
|
1005
1030
|
);
|
|
1006
1031
|
|
|
1007
1032
|
return encodedTransaction;
|
package/src/wallet/enum.ts
CHANGED
|
@@ -5,3 +5,13 @@ export enum WalletTypeEnum {
|
|
|
5
5
|
Watch = "watch",
|
|
6
6
|
PrivateKey = "privkey",
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
export enum FeePaidByEnum {
|
|
10
|
+
change = "change",
|
|
11
|
+
first = "firstOutput",
|
|
12
|
+
any = "anyOutputs",
|
|
13
|
+
last = "lastOutput",
|
|
14
|
+
changeThenFirst = "changeThenFirst",
|
|
15
|
+
changeThenAny = "changeThenAny",
|
|
16
|
+
changeThenLast = "changeThenLast",
|
|
17
|
+
}
|