@atomiqlabs/lp-lib 10.3.11
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/LICENSE +201 -0
- package/dist/fees/IBtcFeeEstimator.d.ts +3 -0
- package/dist/fees/IBtcFeeEstimator.js +2 -0
- package/dist/fees/OneDollarFeeEstimator.d.ts +16 -0
- package/dist/fees/OneDollarFeeEstimator.js +71 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +52 -0
- package/dist/info/InfoHandler.d.ts +17 -0
- package/dist/info/InfoHandler.js +70 -0
- package/dist/plugins/IPlugin.d.ts +118 -0
- package/dist/plugins/IPlugin.js +33 -0
- package/dist/plugins/PluginManager.d.ts +89 -0
- package/dist/plugins/PluginManager.js +263 -0
- package/dist/prices/BinanceSwapPrice.d.ts +27 -0
- package/dist/prices/BinanceSwapPrice.js +106 -0
- package/dist/prices/CoinGeckoSwapPrice.d.ts +31 -0
- package/dist/prices/CoinGeckoSwapPrice.js +76 -0
- package/dist/storage/IIntermediaryStorage.d.ts +15 -0
- package/dist/storage/IIntermediaryStorage.js +2 -0
- package/dist/storagemanager/IntermediaryStorageManager.d.ts +15 -0
- package/dist/storagemanager/IntermediaryStorageManager.js +113 -0
- package/dist/storagemanager/StorageManager.d.ts +12 -0
- package/dist/storagemanager/StorageManager.js +74 -0
- package/dist/swaps/FromBtcBaseSwap.d.ts +12 -0
- package/dist/swaps/FromBtcBaseSwap.js +16 -0
- package/dist/swaps/FromBtcBaseSwapHandler.d.ts +118 -0
- package/dist/swaps/FromBtcBaseSwapHandler.js +294 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.d.ts +25 -0
- package/dist/swaps/FromBtcLnBaseSwapHandler.js +55 -0
- package/dist/swaps/ISwapPrice.d.ts +44 -0
- package/dist/swaps/ISwapPrice.js +73 -0
- package/dist/swaps/SwapHandler.d.ts +186 -0
- package/dist/swaps/SwapHandler.js +292 -0
- package/dist/swaps/SwapHandlerSwap.d.ts +75 -0
- package/dist/swaps/SwapHandlerSwap.js +72 -0
- package/dist/swaps/ToBtcBaseSwap.d.ts +35 -0
- package/dist/swaps/ToBtcBaseSwap.js +61 -0
- package/dist/swaps/ToBtcBaseSwapHandler.d.ts +94 -0
- package/dist/swaps/ToBtcBaseSwapHandler.js +233 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.d.ts +92 -0
- package/dist/swaps/frombtc_abstract/FromBtcAbs.js +386 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.d.ts +26 -0
- package/dist/swaps/frombtc_abstract/FromBtcSwapAbs.js +63 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.d.ts +55 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrusted.js +586 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.d.ts +43 -0
- package/dist/swaps/frombtc_trusted/FromBtcTrustedSwap.js +99 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.d.ts +105 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnAbs.js +731 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.d.ts +29 -0
- package/dist/swaps/frombtcln_abstract/FromBtcLnSwapAbs.js +64 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.d.ts +79 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrusted.js +514 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.d.ts +28 -0
- package/dist/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.js +66 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.d.ts +290 -0
- package/dist/swaps/tobtc_abstract/ToBtcAbs.js +1056 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.d.ts +29 -0
- package/dist/swaps/tobtc_abstract/ToBtcSwapAbs.js +70 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.d.ts +246 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnAbs.js +1169 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.d.ts +27 -0
- package/dist/swaps/tobtcln_abstract/ToBtcLnSwapAbs.js +65 -0
- package/dist/utils/Utils.d.ts +32 -0
- package/dist/utils/Utils.js +109 -0
- package/dist/utils/coinselect2/accumulative.d.ts +6 -0
- package/dist/utils/coinselect2/accumulative.js +44 -0
- package/dist/utils/coinselect2/blackjack.d.ts +6 -0
- package/dist/utils/coinselect2/blackjack.js +41 -0
- package/dist/utils/coinselect2/index.d.ts +16 -0
- package/dist/utils/coinselect2/index.js +40 -0
- package/dist/utils/coinselect2/utils.d.ts +64 -0
- package/dist/utils/coinselect2/utils.js +121 -0
- package/dist/utils/paramcoders/IParamReader.d.ts +5 -0
- package/dist/utils/paramcoders/IParamReader.js +2 -0
- package/dist/utils/paramcoders/IParamWriter.d.ts +4 -0
- package/dist/utils/paramcoders/IParamWriter.js +2 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.d.ts +10 -0
- package/dist/utils/paramcoders/LegacyParamEncoder.js +33 -0
- package/dist/utils/paramcoders/ParamDecoder.d.ts +25 -0
- package/dist/utils/paramcoders/ParamDecoder.js +234 -0
- package/dist/utils/paramcoders/ParamEncoder.d.ts +9 -0
- package/dist/utils/paramcoders/ParamEncoder.js +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.d.ts +22 -0
- package/dist/utils/paramcoders/SchemaVerifier.js +85 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.d.ts +8 -0
- package/dist/utils/paramcoders/server/ServerParamDecoder.js +105 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.d.ts +11 -0
- package/dist/utils/paramcoders/server/ServerParamEncoder.js +76 -0
- package/package.json +43 -0
- package/src/fees/IBtcFeeEstimator.ts +7 -0
- package/src/fees/OneDollarFeeEstimator.ts +95 -0
- package/src/index.ts +46 -0
- package/src/info/InfoHandler.ts +106 -0
- package/src/plugins/IPlugin.ts +155 -0
- package/src/plugins/PluginManager.ts +310 -0
- package/src/prices/BinanceSwapPrice.ts +114 -0
- package/src/prices/CoinGeckoSwapPrice.ts +88 -0
- package/src/storage/IIntermediaryStorage.ts +21 -0
- package/src/storagemanager/IntermediaryStorageManager.ts +101 -0
- package/src/storagemanager/StorageManager.ts +68 -0
- package/src/swaps/FromBtcBaseSwap.ts +21 -0
- package/src/swaps/FromBtcBaseSwapHandler.ts +375 -0
- package/src/swaps/FromBtcLnBaseSwapHandler.ts +48 -0
- package/src/swaps/ISwapPrice.ts +94 -0
- package/src/swaps/SwapHandler.ts +404 -0
- package/src/swaps/SwapHandlerSwap.ts +133 -0
- package/src/swaps/ToBtcBaseSwap.ts +76 -0
- package/src/swaps/ToBtcBaseSwapHandler.ts +309 -0
- package/src/swaps/frombtc_abstract/FromBtcAbs.ts +484 -0
- package/src/swaps/frombtc_abstract/FromBtcSwapAbs.ts +77 -0
- package/src/swaps/frombtc_trusted/FromBtcTrusted.ts +661 -0
- package/src/swaps/frombtc_trusted/FromBtcTrustedSwap.ts +158 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnAbs.ts +864 -0
- package/src/swaps/frombtcln_abstract/FromBtcLnSwapAbs.ts +82 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrusted.ts +592 -0
- package/src/swaps/frombtcln_trusted/FromBtcLnTrustedSwap.ts +90 -0
- package/src/swaps/tobtc_abstract/ToBtcAbs.ts +1249 -0
- package/src/swaps/tobtc_abstract/ToBtcSwapAbs.ts +112 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnAbs.ts +1422 -0
- package/src/swaps/tobtcln_abstract/ToBtcLnSwapAbs.ts +87 -0
- package/src/utils/Utils.ts +108 -0
- package/src/utils/coinselect2/accumulative.js +32 -0
- package/src/utils/coinselect2/accumulative.ts +58 -0
- package/src/utils/coinselect2/blackjack.js +29 -0
- package/src/utils/coinselect2/blackjack.ts +54 -0
- package/src/utils/coinselect2/index.js +16 -0
- package/src/utils/coinselect2/index.ts +50 -0
- package/src/utils/coinselect2/utils.js +110 -0
- package/src/utils/coinselect2/utils.ts +183 -0
- package/src/utils/paramcoders/IParamReader.ts +8 -0
- package/src/utils/paramcoders/IParamWriter.ts +8 -0
- package/src/utils/paramcoders/LegacyParamEncoder.ts +28 -0
- package/src/utils/paramcoders/ParamDecoder.ts +219 -0
- package/src/utils/paramcoders/ParamEncoder.ts +30 -0
- package/src/utils/paramcoders/SchemaVerifier.ts +97 -0
- package/src/utils/paramcoders/server/ServerParamDecoder.ts +115 -0
- package/src/utils/paramcoders/server/ServerParamEncoder.ts +76 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as BN from "bn.js";
|
|
2
|
+
import * as bolt11 from "@atomiqlabs/bolt11";
|
|
3
|
+
import {SwapData} from "@atomiqlabs/base";
|
|
4
|
+
import {SwapHandlerType} from "../..";
|
|
5
|
+
import {deserializeBN, serializeBN} from "../../utils/Utils";
|
|
6
|
+
import {ToBtcBaseSwap} from "../ToBtcBaseSwap";
|
|
7
|
+
|
|
8
|
+
export enum ToBtcLnSwapState {
|
|
9
|
+
REFUNDED = -3,
|
|
10
|
+
CANCELED = -2,
|
|
11
|
+
NON_PAYABLE = -1,
|
|
12
|
+
SAVED = 0,
|
|
13
|
+
COMMITED = 1,
|
|
14
|
+
PAID = 2,
|
|
15
|
+
CLAIMED = 3
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ToBtcLnSwapAbs<T extends SwapData = SwapData> extends ToBtcBaseSwap<T, ToBtcLnSwapState> {
|
|
19
|
+
|
|
20
|
+
readonly pr: string;
|
|
21
|
+
readonly signatureExpiry: BN;
|
|
22
|
+
|
|
23
|
+
secret: string;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
chainIdentifier: string,
|
|
27
|
+
pr: string,
|
|
28
|
+
swapFee: BN,
|
|
29
|
+
swapFeeInToken: BN,
|
|
30
|
+
quotedNetworkFee: BN,
|
|
31
|
+
quotedNetworkFeeInToken: BN,
|
|
32
|
+
signatureExpiry: BN
|
|
33
|
+
);
|
|
34
|
+
constructor(obj: any);
|
|
35
|
+
|
|
36
|
+
constructor(chainIdOrObj: string | any, pr?: string, swapFee?: BN, swapFeeInToken?: BN, quotedNetworkFee?: BN, quotedNetworkFeeInToken?: BN, signatureExpiry?: BN) {
|
|
37
|
+
if(typeof(chainIdOrObj)==="string") {
|
|
38
|
+
super(chainIdOrObj, swapFee, swapFeeInToken, quotedNetworkFee, quotedNetworkFeeInToken);
|
|
39
|
+
this.state = ToBtcLnSwapState.SAVED;
|
|
40
|
+
this.pr = pr;
|
|
41
|
+
this.signatureExpiry = signatureExpiry;
|
|
42
|
+
} else {
|
|
43
|
+
super(chainIdOrObj);
|
|
44
|
+
this.pr = chainIdOrObj.pr;
|
|
45
|
+
this.signatureExpiry = deserializeBN(chainIdOrObj.signatureExpiry);
|
|
46
|
+
this.secret = chainIdOrObj.secret;
|
|
47
|
+
|
|
48
|
+
//Compatibility with older versions
|
|
49
|
+
this.quotedNetworkFee ??= deserializeBN(chainIdOrObj.maxFee);
|
|
50
|
+
this.realNetworkFee ??= deserializeBN(chainIdOrObj.realRoutingFee);
|
|
51
|
+
}
|
|
52
|
+
this.type = SwapHandlerType.TO_BTCLN;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
serialize(): any {
|
|
56
|
+
const partialSerialized = super.serialize();
|
|
57
|
+
partialSerialized.pr = this.pr;
|
|
58
|
+
partialSerialized.signatureExpiry = serializeBN(this.signatureExpiry);
|
|
59
|
+
partialSerialized.secret = this.secret;
|
|
60
|
+
return partialSerialized;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getHash(): string {
|
|
64
|
+
return bolt11.decode(this.pr).tagsObject.payment_hash;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getHashBuffer(): Buffer {
|
|
68
|
+
return Buffer.from(bolt11.decode(this.pr).tagsObject.payment_hash, "hex");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isInitiated(): boolean {
|
|
72
|
+
return this.state!==ToBtcLnSwapState.SAVED;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isFailed(): boolean {
|
|
76
|
+
return this.state===ToBtcLnSwapState.NON_PAYABLE || this.state===ToBtcLnSwapState.CANCELED || this.state===ToBtcLnSwapState.REFUNDED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isSuccess(): boolean {
|
|
80
|
+
return this.state===ToBtcLnSwapState.CLAIMED;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getOutputAmount(): BN {
|
|
84
|
+
return new BN(bolt11.decode(this.pr).millisatoshis).add(new BN(999)).div(new BN(1000));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {Request, Response} from "express";
|
|
2
|
+
import {ServerParamEncoder} from "./paramcoders/server/ServerParamEncoder";
|
|
3
|
+
import * as BN from "bn.js";
|
|
4
|
+
|
|
5
|
+
export type DefinedRuntimeError = {
|
|
6
|
+
code: number;
|
|
7
|
+
msg?: string;
|
|
8
|
+
_httpStatus?: number;
|
|
9
|
+
data?: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function isDefinedRuntimeError(obj: any): obj is DefinedRuntimeError {
|
|
13
|
+
if(obj.code!=null && typeof(obj.code)==="number") {
|
|
14
|
+
if(obj.msg!=null && typeof(obj.msg)!=="string") return false;
|
|
15
|
+
if(obj._httpStatus!=null && typeof(obj._httpStatus)!=="number") return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function expressHandlerWrapper(func: (
|
|
22
|
+
req: Request,
|
|
23
|
+
res: Response
|
|
24
|
+
) => Promise<void>) : ((
|
|
25
|
+
req: Request,
|
|
26
|
+
res: Response & {responseStream: ServerParamEncoder}
|
|
27
|
+
) => void) {
|
|
28
|
+
return (
|
|
29
|
+
req: Request,
|
|
30
|
+
res: Response & {responseStream: ServerParamEncoder}
|
|
31
|
+
) => {
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
await func(req, res);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error(e);
|
|
37
|
+
let statusCode = 500;
|
|
38
|
+
const obj: {code: number, msg: string, data?: any} = {
|
|
39
|
+
code: 0,
|
|
40
|
+
msg: "Internal server error"
|
|
41
|
+
};
|
|
42
|
+
if(isDefinedRuntimeError(e)) {
|
|
43
|
+
obj.msg = e.msg;
|
|
44
|
+
obj.code = e.code;
|
|
45
|
+
obj.data = e.data;
|
|
46
|
+
statusCode = 400;
|
|
47
|
+
if(e._httpStatus!=null) statusCode = e._httpStatus;
|
|
48
|
+
}
|
|
49
|
+
if(res.responseStream!=null) {
|
|
50
|
+
if(res.responseStream.getAbortSignal().aborted) return;
|
|
51
|
+
res.responseStream.writeParamsAndEnd(obj).catch(e => null);
|
|
52
|
+
} else {
|
|
53
|
+
res.status(statusCode).json(obj);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getLogger(prefix: string) {
|
|
61
|
+
return {
|
|
62
|
+
debug: (msg, ...args) => console.debug(prefix+msg, ...args),
|
|
63
|
+
info: (msg, ...args) => console.info(prefix+msg, ...args),
|
|
64
|
+
warn: (msg, ...args) => console.warn(prefix+msg, ...args),
|
|
65
|
+
error: (msg, ...args) => console.error(prefix+msg, ...args)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const HEX_REGEX = /[0-9a-fA-F]+/;
|
|
70
|
+
|
|
71
|
+
export function shuffle(array: any[]) {
|
|
72
|
+
let currentIndex = array.length;
|
|
73
|
+
|
|
74
|
+
// While there remain elements to shuffle...
|
|
75
|
+
while (currentIndex != 0) {
|
|
76
|
+
|
|
77
|
+
// Pick a remaining element...
|
|
78
|
+
let randomIndex = Math.floor(Math.random() * currentIndex);
|
|
79
|
+
currentIndex--;
|
|
80
|
+
|
|
81
|
+
// And swap it with the current element.
|
|
82
|
+
[array[currentIndex], array[randomIndex]] = [
|
|
83
|
+
array[randomIndex], array[currentIndex]];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function serializeBN(bn: BN | null): string | null {
|
|
88
|
+
return bn==null ? null : bn.toString(10);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function deserializeBN(str: string | null): BN | null {
|
|
92
|
+
return str==null ? null : new BN(str);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Handles & throws LND error if the error is:
|
|
97
|
+
* - network error
|
|
98
|
+
* - server side (LND) internal error
|
|
99
|
+
* - malformed input data error
|
|
100
|
+
*
|
|
101
|
+
* @param e
|
|
102
|
+
*/
|
|
103
|
+
export function handleLndError(e: any) {
|
|
104
|
+
if(!Array.isArray(e)) throw e; //Throw errors that are not originating from the SDK
|
|
105
|
+
if(typeof(e[0])!=="number") throw e; //Throw errors that don't have proper format
|
|
106
|
+
if(e[0]>=500 && e[0]<600) throw e; //Throw server errors 5xx
|
|
107
|
+
if(e[0]===400) throw e; //Throw malformed request data errors
|
|
108
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { utils } from "./utils";
|
|
2
|
+
// add inputs until we reach or surpass the target value (or deplete)
|
|
3
|
+
// worst-case: O(n)
|
|
4
|
+
export function accumulative(utxos, outputs, feeRate, type) {
|
|
5
|
+
if (!isFinite(utils.uintOrNaN(feeRate)))
|
|
6
|
+
return null;
|
|
7
|
+
let bytesAccum = utils.transactionBytes([], outputs, type);
|
|
8
|
+
let inAccum = 0;
|
|
9
|
+
const inputs = [];
|
|
10
|
+
const outAccum = utils.sumOrNaN(outputs);
|
|
11
|
+
for (let i = 0; i < utxos.length; ++i) {
|
|
12
|
+
const utxo = utxos[i];
|
|
13
|
+
const utxoBytes = utils.inputBytes(utxo);
|
|
14
|
+
const utxoFee = feeRate * utxoBytes;
|
|
15
|
+
const utxoValue = utils.uintOrNaN(utxo.value);
|
|
16
|
+
// skip detrimental input
|
|
17
|
+
if (utxoFee > utxo.value) {
|
|
18
|
+
if (i === utxos.length - 1)
|
|
19
|
+
return { fee: feeRate * (bytesAccum + utxoBytes) };
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
bytesAccum += utxoBytes;
|
|
23
|
+
inAccum += utxoValue;
|
|
24
|
+
inputs.push(utxo);
|
|
25
|
+
const fee = feeRate * bytesAccum;
|
|
26
|
+
// go again?
|
|
27
|
+
if (inAccum < outAccum + fee)
|
|
28
|
+
continue;
|
|
29
|
+
return utils.finalize(inputs, outputs, feeRate, type);
|
|
30
|
+
}
|
|
31
|
+
return { fee: feeRate * bytesAccum };
|
|
32
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {CoinselectAddressTypes, CoinselectTxInput, CoinselectTxOutput, utils} from "./utils";
|
|
2
|
+
|
|
3
|
+
// add inputs until we reach or surpass the target value (or deplete)
|
|
4
|
+
// worst-case: O(n)
|
|
5
|
+
export function accumulative (
|
|
6
|
+
utxos: CoinselectTxInput[],
|
|
7
|
+
outputs: CoinselectTxOutput[],
|
|
8
|
+
feeRate: number,
|
|
9
|
+
type: CoinselectAddressTypes,
|
|
10
|
+
requiredInputs?: CoinselectTxInput[]
|
|
11
|
+
): {
|
|
12
|
+
inputs?: CoinselectTxInput[],
|
|
13
|
+
outputs?: CoinselectTxOutput[],
|
|
14
|
+
fee: number
|
|
15
|
+
} {
|
|
16
|
+
if (!isFinite(utils.uintOrNaN(feeRate))) return null;
|
|
17
|
+
|
|
18
|
+
let bytesAccum = utils.transactionBytes([], outputs, type);
|
|
19
|
+
let inAccum = 0;
|
|
20
|
+
const inputs = [];
|
|
21
|
+
|
|
22
|
+
if(requiredInputs!=null) for(let utxo of requiredInputs) {
|
|
23
|
+
const {length: utxoBytes} = utils.inputBytes(utxo);
|
|
24
|
+
const utxoValue = utils.uintOrNaN(utxo.value);
|
|
25
|
+
|
|
26
|
+
bytesAccum += utxoBytes;
|
|
27
|
+
inAccum += utxoValue;
|
|
28
|
+
inputs.push(utxo);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const outAccum = utils.sumOrNaN(outputs);
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < utxos.length; ++i) {
|
|
34
|
+
const utxo = utxos[i];
|
|
35
|
+
const {length: utxoBytes} = utils.inputBytes(utxo);
|
|
36
|
+
const utxoFee = feeRate * utxoBytes;
|
|
37
|
+
const utxoValue = utils.uintOrNaN(utxo.value);
|
|
38
|
+
|
|
39
|
+
// skip detrimental input
|
|
40
|
+
if (utxoFee > utxo.value) {
|
|
41
|
+
if (i === utxos.length - 1) return { fee: feeRate * (bytesAccum + utxoBytes) };
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
bytesAccum += utxoBytes;
|
|
46
|
+
inAccum += utxoValue;
|
|
47
|
+
inputs.push(utxo);
|
|
48
|
+
|
|
49
|
+
const fee = feeRate * bytesAccum;
|
|
50
|
+
|
|
51
|
+
// go again?
|
|
52
|
+
if (inAccum < outAccum + fee) continue;
|
|
53
|
+
|
|
54
|
+
return utils.finalize(inputs, outputs, feeRate, type);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { fee: feeRate * bytesAccum };
|
|
58
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { utils } from "./utils";
|
|
2
|
+
// add inputs until we reach or surpass the target value (or deplete)
|
|
3
|
+
// worst-case: O(n)
|
|
4
|
+
export function blackjack(utxos, outputs, feeRate, type) {
|
|
5
|
+
if (!isFinite(utils.uintOrNaN(feeRate)))
|
|
6
|
+
return null;
|
|
7
|
+
let bytesAccum = utils.transactionBytes([], outputs, type);
|
|
8
|
+
let inAccum = 0;
|
|
9
|
+
const inputs = [];
|
|
10
|
+
const outAccum = utils.sumOrNaN(outputs);
|
|
11
|
+
const threshold = utils.dustThreshold({ type });
|
|
12
|
+
for (let i = 0; i < utxos.length; ++i) {
|
|
13
|
+
const input = utxos[i];
|
|
14
|
+
const inputBytes = utils.inputBytes(input);
|
|
15
|
+
const fee = feeRate * (bytesAccum + inputBytes);
|
|
16
|
+
const inputValue = utils.uintOrNaN(input.value);
|
|
17
|
+
// would it waste value?
|
|
18
|
+
if ((inAccum + inputValue) > (outAccum + fee + threshold))
|
|
19
|
+
continue;
|
|
20
|
+
bytesAccum += inputBytes;
|
|
21
|
+
inAccum += inputValue;
|
|
22
|
+
inputs.push(input);
|
|
23
|
+
// go again?
|
|
24
|
+
if (inAccum < outAccum + fee)
|
|
25
|
+
continue;
|
|
26
|
+
return utils.finalize(inputs, outputs, feeRate, type);
|
|
27
|
+
}
|
|
28
|
+
return { fee: feeRate * bytesAccum };
|
|
29
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {CoinselectAddressTypes, CoinselectTxInput, CoinselectTxOutput, utils} from "./utils";
|
|
2
|
+
|
|
3
|
+
// add inputs until we reach or surpass the target value (or deplete)
|
|
4
|
+
// worst-case: O(n)
|
|
5
|
+
export function blackjack (
|
|
6
|
+
utxos: CoinselectTxInput[],
|
|
7
|
+
outputs: CoinselectTxOutput[],
|
|
8
|
+
feeRate: number,
|
|
9
|
+
type: CoinselectAddressTypes,
|
|
10
|
+
requiredInputs?: CoinselectTxInput[]
|
|
11
|
+
): {
|
|
12
|
+
inputs?: CoinselectTxInput[],
|
|
13
|
+
outputs?: CoinselectTxOutput[],
|
|
14
|
+
fee: number
|
|
15
|
+
} {
|
|
16
|
+
if (!isFinite(utils.uintOrNaN(feeRate))) return null;
|
|
17
|
+
|
|
18
|
+
let bytesAccum = utils.transactionBytes([], outputs, type);
|
|
19
|
+
let inAccum = 0;
|
|
20
|
+
const inputs = [];
|
|
21
|
+
|
|
22
|
+
if(requiredInputs!=null) for(let utxo of requiredInputs) {
|
|
23
|
+
const {length: utxoBytes} = utils.inputBytes(utxo);
|
|
24
|
+
const utxoValue = utils.uintOrNaN(utxo.value);
|
|
25
|
+
|
|
26
|
+
bytesAccum += utxoBytes;
|
|
27
|
+
inAccum += utxoValue;
|
|
28
|
+
inputs.push(utxo);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const outAccum = utils.sumOrNaN(outputs);
|
|
32
|
+
const threshold = utils.dustThreshold({type});
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < utxos.length; ++i) {
|
|
35
|
+
const input = utxos[i];
|
|
36
|
+
const {length: inputBytes} = utils.inputBytes(input);
|
|
37
|
+
const fee = feeRate * (bytesAccum + inputBytes);
|
|
38
|
+
const inputValue = utils.uintOrNaN(input.value);
|
|
39
|
+
|
|
40
|
+
// would it waste value?
|
|
41
|
+
if ((inAccum + inputValue) > (outAccum + fee + threshold)) continue;
|
|
42
|
+
|
|
43
|
+
bytesAccum += inputBytes;
|
|
44
|
+
inAccum += inputValue;
|
|
45
|
+
inputs.push(input);
|
|
46
|
+
|
|
47
|
+
// go again?
|
|
48
|
+
if (inAccum < outAccum + fee) continue;
|
|
49
|
+
|
|
50
|
+
return utils.finalize(inputs, outputs, feeRate, type);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { fee: feeRate * bytesAccum };
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { accumulative } from "./accumulative";
|
|
2
|
+
import { blackjack } from "./blackjack";
|
|
3
|
+
import { utils } from "./utils";
|
|
4
|
+
// order by descending value, minus the inputs approximate fee
|
|
5
|
+
function utxoScore(x, feeRate) {
|
|
6
|
+
return x.value - (feeRate * utils.inputBytes(x));
|
|
7
|
+
}
|
|
8
|
+
export function coinSelect(utxos, outputs, feeRate, type) {
|
|
9
|
+
utxos = utxos.sort((a, b) => utxoScore(b, feeRate) - utxoScore(a, feeRate));
|
|
10
|
+
// attempt to use the blackjack strategy first (no change output)
|
|
11
|
+
const base = blackjack(utxos, outputs, feeRate, type);
|
|
12
|
+
if (base.inputs)
|
|
13
|
+
return base;
|
|
14
|
+
// else, try the accumulative strategy
|
|
15
|
+
return accumulative(utxos, outputs, feeRate, type);
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {accumulative} from "./accumulative"
|
|
2
|
+
import {blackjack} from "./blackjack"
|
|
3
|
+
import {CoinselectAddressTypes, CoinselectTxInput, CoinselectTxOutput, utils} from "./utils"
|
|
4
|
+
import * as BN from "bn.js";
|
|
5
|
+
import {shuffle} from "../Utils";
|
|
6
|
+
|
|
7
|
+
// order by descending value, minus the inputs approximate fee
|
|
8
|
+
function utxoScore (x, feeRate) {
|
|
9
|
+
return x.value - (feeRate * utils.inputBytes(x).length)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function utxoFeePPM(utxo: CoinselectTxInput, feeRate: number): number {
|
|
13
|
+
return new BN(utxo.value).mul(new BN(1000000)).div(new BN(Math.ceil(feeRate * utils.inputBytes(utxo).length))).toNumber();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Runs a coinselection algorithm on given inputs, outputs and fee rate
|
|
18
|
+
*
|
|
19
|
+
* @param utxos Utxo pool to select additional inputs from
|
|
20
|
+
* @param outputs Outputs of the transaction
|
|
21
|
+
* @param feeRate Feerate in sats/vB
|
|
22
|
+
* @param changeType Change address type
|
|
23
|
+
* @param requiredInputs Utxos that need to be included as inputs to the transaction
|
|
24
|
+
* @param randomize Randomize the UTXO order before running the coinselection algorithm
|
|
25
|
+
*/
|
|
26
|
+
export function coinSelect (
|
|
27
|
+
utxos: CoinselectTxInput[],
|
|
28
|
+
outputs: CoinselectTxOutput[],
|
|
29
|
+
feeRate: number,
|
|
30
|
+
changeType: CoinselectAddressTypes,
|
|
31
|
+
requiredInputs?: CoinselectTxInput[],
|
|
32
|
+
randomize?: boolean
|
|
33
|
+
): {
|
|
34
|
+
inputs?: CoinselectTxInput[],
|
|
35
|
+
outputs?: CoinselectTxOutput[],
|
|
36
|
+
fee: number
|
|
37
|
+
} {
|
|
38
|
+
if(randomize) {
|
|
39
|
+
shuffle(utxos);
|
|
40
|
+
} else {
|
|
41
|
+
utxos.sort((a, b) => utxoScore(b, feeRate) - utxoScore(a, feeRate));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// attempt to use the blackjack strategy first (no change output)
|
|
45
|
+
let base = blackjack(utxos, outputs, feeRate, changeType, requiredInputs);
|
|
46
|
+
if (base.inputs) return base;
|
|
47
|
+
|
|
48
|
+
// else, try the accumulative strategy
|
|
49
|
+
return accumulative(utxos, outputs, feeRate, changeType, requiredInputs);
|
|
50
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// baseline estimates, used to improve performance
|
|
2
|
+
const TX_EMPTY_SIZE = 4 + 1 + 1 + 4;
|
|
3
|
+
const TX_INPUT_BASE = 32 + 4 + 1 + 4;
|
|
4
|
+
const WITNESS_OVERHEAD = 2 / 4;
|
|
5
|
+
const P2WPKH_WITNESS = (1 + 1 + 72 + 1 + 33) / 4;
|
|
6
|
+
const P2TR_WITNESS = (1 + 1 + 65) / 4;
|
|
7
|
+
const TX_INPUT_PUBKEYHASH = 107;
|
|
8
|
+
const TX_INPUT_P2SH_P2WPKH = 23 + P2WPKH_WITNESS + 1;
|
|
9
|
+
const TX_INPUT_P2WPKH = 0 + P2WPKH_WITNESS;
|
|
10
|
+
const TX_INPUT_P2WSH = 0 + (1 + 1 + 64) / 4;
|
|
11
|
+
const TX_INPUT_P2TR = 0 + P2TR_WITNESS;
|
|
12
|
+
const TX_OUTPUT_BASE = 8 + 1;
|
|
13
|
+
const TX_OUTPUT_PUBKEYHASH = 25;
|
|
14
|
+
const TX_OUTPUT_P2SH_P2WPKH = 23;
|
|
15
|
+
const TX_OUTPUT_P2WPKH = 22;
|
|
16
|
+
const TX_OUTPUT_P2WSH = 34;
|
|
17
|
+
const TX_OUTPUT_P2TR = 34;
|
|
18
|
+
const INPUT_BYTES = {
|
|
19
|
+
"p2sh-p2wpkh": TX_INPUT_P2SH_P2WPKH,
|
|
20
|
+
"p2wpkh": TX_INPUT_P2WPKH,
|
|
21
|
+
"p2tr": TX_INPUT_P2TR,
|
|
22
|
+
"p2pkh": TX_INPUT_PUBKEYHASH,
|
|
23
|
+
"p2wsh": TX_INPUT_P2WSH
|
|
24
|
+
};
|
|
25
|
+
function inputBytes(input) {
|
|
26
|
+
return TX_INPUT_BASE + (input.script ? input.script.length : INPUT_BYTES[input.type]);
|
|
27
|
+
}
|
|
28
|
+
const OUTPUT_BYTES = {
|
|
29
|
+
"p2sh-p2wpkh": TX_OUTPUT_P2SH_P2WPKH,
|
|
30
|
+
"p2wpkh": TX_OUTPUT_P2WPKH,
|
|
31
|
+
"p2tr": TX_OUTPUT_P2TR,
|
|
32
|
+
"p2pkh": TX_OUTPUT_PUBKEYHASH,
|
|
33
|
+
"p2wsh": TX_OUTPUT_P2WSH
|
|
34
|
+
};
|
|
35
|
+
function outputBytes(output) {
|
|
36
|
+
return TX_OUTPUT_BASE + (output.script ? output.script.length : OUTPUT_BYTES[output.type]);
|
|
37
|
+
}
|
|
38
|
+
const DUST_THRESHOLDS = {
|
|
39
|
+
"p2sh-p2wpkh": 540,
|
|
40
|
+
"p2wpkh": 294,
|
|
41
|
+
"p2tr": 330,
|
|
42
|
+
"p2pkh": 546,
|
|
43
|
+
"p2wsh": 330
|
|
44
|
+
};
|
|
45
|
+
function dustThreshold(output) {
|
|
46
|
+
return DUST_THRESHOLDS[output.type];
|
|
47
|
+
}
|
|
48
|
+
function transactionBytes(inputs, outputs, changeType) {
|
|
49
|
+
let size = TX_EMPTY_SIZE;
|
|
50
|
+
let isSegwit = false;
|
|
51
|
+
if (changeType !== "p2pkh") {
|
|
52
|
+
size += WITNESS_OVERHEAD;
|
|
53
|
+
let isSegwit = true;
|
|
54
|
+
}
|
|
55
|
+
for (let input of inputs) {
|
|
56
|
+
if (!isSegwit && (input.type !== "p2pkh")) {
|
|
57
|
+
isSegwit = true;
|
|
58
|
+
size += WITNESS_OVERHEAD;
|
|
59
|
+
}
|
|
60
|
+
size += inputBytes(input);
|
|
61
|
+
}
|
|
62
|
+
for (let output of outputs) {
|
|
63
|
+
size += outputBytes(output);
|
|
64
|
+
}
|
|
65
|
+
return Math.ceil(size);
|
|
66
|
+
}
|
|
67
|
+
function uintOrNaN(v) {
|
|
68
|
+
if (typeof v !== 'number')
|
|
69
|
+
return NaN;
|
|
70
|
+
if (!isFinite(v))
|
|
71
|
+
return NaN;
|
|
72
|
+
if (Math.floor(v) !== v)
|
|
73
|
+
return NaN;
|
|
74
|
+
if (v < 0)
|
|
75
|
+
return NaN;
|
|
76
|
+
return v;
|
|
77
|
+
}
|
|
78
|
+
function sumForgiving(range) {
|
|
79
|
+
return range.reduce((a, x) => a + (isFinite(x.value) ? x.value : 0), 0);
|
|
80
|
+
}
|
|
81
|
+
function sumOrNaN(range) {
|
|
82
|
+
return range.reduce((a, x) => a + uintOrNaN(x.value), 0);
|
|
83
|
+
}
|
|
84
|
+
function finalize(inputs, outputs, feeRate, changeType) {
|
|
85
|
+
const bytesAccum = transactionBytes(inputs, outputs, changeType);
|
|
86
|
+
const feeAfterExtraOutput = feeRate * (bytesAccum + outputBytes({ type: changeType }));
|
|
87
|
+
const remainderAfterExtraOutput = sumOrNaN(inputs) - (sumOrNaN(outputs) + feeAfterExtraOutput);
|
|
88
|
+
// is it worth a change output?
|
|
89
|
+
if (remainderAfterExtraOutput >= dustThreshold({ type: changeType })) {
|
|
90
|
+
outputs = outputs.concat({ value: remainderAfterExtraOutput, type: changeType });
|
|
91
|
+
}
|
|
92
|
+
const fee = sumOrNaN(inputs) - sumOrNaN(outputs);
|
|
93
|
+
if (!isFinite(fee))
|
|
94
|
+
return { fee: feeRate * bytesAccum };
|
|
95
|
+
return {
|
|
96
|
+
inputs: inputs,
|
|
97
|
+
outputs: outputs,
|
|
98
|
+
fee: fee
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export const utils = {
|
|
102
|
+
dustThreshold: dustThreshold,
|
|
103
|
+
finalize: finalize,
|
|
104
|
+
inputBytes: inputBytes,
|
|
105
|
+
outputBytes: outputBytes,
|
|
106
|
+
sumOrNaN: sumOrNaN,
|
|
107
|
+
sumForgiving: sumForgiving,
|
|
108
|
+
transactionBytes: transactionBytes,
|
|
109
|
+
uintOrNaN: uintOrNaN
|
|
110
|
+
};
|