@arkade-os/sdk 0.0.16
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/README.md +312 -0
- package/dist/cjs/arknote/index.js +86 -0
- package/dist/cjs/forfeit.js +38 -0
- package/dist/cjs/identity/inMemoryKey.js +40 -0
- package/dist/cjs/identity/index.js +2 -0
- package/dist/cjs/index.js +48 -0
- package/dist/cjs/musig2/index.js +10 -0
- package/dist/cjs/musig2/keys.js +57 -0
- package/dist/cjs/musig2/nonces.js +44 -0
- package/dist/cjs/musig2/sign.js +102 -0
- package/dist/cjs/networks.js +26 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/providers/ark.js +530 -0
- package/dist/cjs/providers/onchain.js +61 -0
- package/dist/cjs/script/address.js +45 -0
- package/dist/cjs/script/base.js +51 -0
- package/dist/cjs/script/default.js +40 -0
- package/dist/cjs/script/tapscript.js +528 -0
- package/dist/cjs/script/vhtlc.js +84 -0
- package/dist/cjs/tree/signingSession.js +238 -0
- package/dist/cjs/tree/validation.js +184 -0
- package/dist/cjs/tree/vtxoTree.js +197 -0
- package/dist/cjs/utils/bip21.js +114 -0
- package/dist/cjs/utils/coinselect.js +73 -0
- package/dist/cjs/utils/psbt.js +124 -0
- package/dist/cjs/utils/transactionHistory.js +148 -0
- package/dist/cjs/utils/txSizeEstimator.js +95 -0
- package/dist/cjs/wallet/index.js +8 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/idb.js +153 -0
- package/dist/cjs/wallet/serviceWorker/db/vtxo/index.js +2 -0
- package/dist/cjs/wallet/serviceWorker/request.js +75 -0
- package/dist/cjs/wallet/serviceWorker/response.js +187 -0
- package/dist/cjs/wallet/serviceWorker/wallet.js +332 -0
- package/dist/cjs/wallet/serviceWorker/worker.js +452 -0
- package/dist/cjs/wallet/wallet.js +720 -0
- package/dist/esm/arknote/index.js +81 -0
- package/dist/esm/forfeit.js +35 -0
- package/dist/esm/identity/inMemoryKey.js +36 -0
- package/dist/esm/identity/index.js +1 -0
- package/dist/esm/index.js +39 -0
- package/dist/esm/musig2/index.js +3 -0
- package/dist/esm/musig2/keys.js +21 -0
- package/dist/esm/musig2/nonces.js +8 -0
- package/dist/esm/musig2/sign.js +63 -0
- package/dist/esm/networks.js +22 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/providers/ark.js +526 -0
- package/dist/esm/providers/onchain.js +57 -0
- package/dist/esm/script/address.js +41 -0
- package/dist/esm/script/base.js +46 -0
- package/dist/esm/script/default.js +37 -0
- package/dist/esm/script/tapscript.js +491 -0
- package/dist/esm/script/vhtlc.js +81 -0
- package/dist/esm/tree/signingSession.js +200 -0
- package/dist/esm/tree/validation.js +179 -0
- package/dist/esm/tree/vtxoTree.js +157 -0
- package/dist/esm/utils/bip21.js +110 -0
- package/dist/esm/utils/coinselect.js +69 -0
- package/dist/esm/utils/psbt.js +118 -0
- package/dist/esm/utils/transactionHistory.js +145 -0
- package/dist/esm/utils/txSizeEstimator.js +91 -0
- package/dist/esm/wallet/index.js +5 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/idb.js +149 -0
- package/dist/esm/wallet/serviceWorker/db/vtxo/index.js +1 -0
- package/dist/esm/wallet/serviceWorker/request.js +72 -0
- package/dist/esm/wallet/serviceWorker/response.js +184 -0
- package/dist/esm/wallet/serviceWorker/wallet.js +328 -0
- package/dist/esm/wallet/serviceWorker/worker.js +448 -0
- package/dist/esm/wallet/wallet.js +716 -0
- package/dist/types/arknote/index.d.ts +17 -0
- package/dist/types/forfeit.d.ts +15 -0
- package/dist/types/identity/inMemoryKey.d.ts +12 -0
- package/dist/types/identity/index.d.ts +7 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/musig2/index.d.ts +4 -0
- package/dist/types/musig2/keys.d.ts +9 -0
- package/dist/types/musig2/nonces.d.ts +13 -0
- package/dist/types/musig2/sign.d.ts +27 -0
- package/dist/types/networks.d.ts +16 -0
- package/dist/types/providers/ark.d.ts +126 -0
- package/dist/types/providers/onchain.d.ts +36 -0
- package/dist/types/script/address.d.ts +10 -0
- package/dist/types/script/base.d.ts +26 -0
- package/dist/types/script/default.d.ts +19 -0
- package/dist/types/script/tapscript.d.ts +94 -0
- package/dist/types/script/vhtlc.d.ts +31 -0
- package/dist/types/tree/signingSession.d.ts +32 -0
- package/dist/types/tree/validation.d.ts +22 -0
- package/dist/types/tree/vtxoTree.d.ts +32 -0
- package/dist/types/utils/bip21.d.ts +21 -0
- package/dist/types/utils/coinselect.d.ts +21 -0
- package/dist/types/utils/psbt.d.ts +11 -0
- package/dist/types/utils/transactionHistory.d.ts +2 -0
- package/dist/types/utils/txSizeEstimator.d.ts +27 -0
- package/dist/types/wallet/index.d.ts +122 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/idb.d.ts +18 -0
- package/dist/types/wallet/serviceWorker/db/vtxo/index.d.ts +12 -0
- package/dist/types/wallet/serviceWorker/request.d.ts +68 -0
- package/dist/types/wallet/serviceWorker/response.d.ts +107 -0
- package/dist/types/wallet/serviceWorker/wallet.d.ts +23 -0
- package/dist/types/wallet/serviceWorker/worker.d.ts +26 -0
- package/dist/types/wallet/wallet.d.ts +42 -0
- package/package.json +88 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BIP21 = exports.BIP21Error = void 0;
|
|
4
|
+
var BIP21Error;
|
|
5
|
+
(function (BIP21Error) {
|
|
6
|
+
BIP21Error["INVALID_URI"] = "Invalid BIP21 URI";
|
|
7
|
+
BIP21Error["INVALID_ADDRESS"] = "Invalid address";
|
|
8
|
+
})(BIP21Error || (exports.BIP21Error = BIP21Error = {}));
|
|
9
|
+
class BIP21 {
|
|
10
|
+
static create(params) {
|
|
11
|
+
const { address, ...options } = params;
|
|
12
|
+
// Build query string
|
|
13
|
+
const queryParams = {};
|
|
14
|
+
for (const [key, value] of Object.entries(options)) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
continue;
|
|
17
|
+
if (key === "amount") {
|
|
18
|
+
if (!isFinite(value)) {
|
|
19
|
+
console.warn("Invalid amount");
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (value < 0) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
queryParams[key] = value;
|
|
26
|
+
}
|
|
27
|
+
else if (key === "ark") {
|
|
28
|
+
// Validate ARK address format
|
|
29
|
+
if (typeof value === "string" &&
|
|
30
|
+
(value.startsWith("ark") || value.startsWith("tark"))) {
|
|
31
|
+
queryParams[key] = value;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.warn("Invalid ARK address format");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (key === "sp") {
|
|
38
|
+
// Validate Silent Payment address format (placeholder)
|
|
39
|
+
if (typeof value === "string" && value.startsWith("sp")) {
|
|
40
|
+
queryParams[key] = value;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.warn("Invalid Silent Payment address format");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (typeof value === "string" || typeof value === "number") {
|
|
47
|
+
queryParams[key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const query = Object.keys(queryParams).length > 0
|
|
51
|
+
? "?" +
|
|
52
|
+
new URLSearchParams(Object.fromEntries(Object.entries(queryParams).map(([k, v]) => [
|
|
53
|
+
k,
|
|
54
|
+
String(v),
|
|
55
|
+
]))).toString()
|
|
56
|
+
: "";
|
|
57
|
+
return `bitcoin:${address ? address.toLowerCase() : ""}${query}`;
|
|
58
|
+
}
|
|
59
|
+
static parse(uri) {
|
|
60
|
+
if (!uri.toLowerCase().startsWith("bitcoin:")) {
|
|
61
|
+
throw new Error(BIP21Error.INVALID_URI);
|
|
62
|
+
}
|
|
63
|
+
// Remove bitcoin: prefix, preserving case of the rest
|
|
64
|
+
const withoutPrefix = uri.slice(uri.toLowerCase().indexOf("bitcoin:") + 8);
|
|
65
|
+
const [address, query] = withoutPrefix.split("?");
|
|
66
|
+
const params = {};
|
|
67
|
+
if (address) {
|
|
68
|
+
params.address = address.toLowerCase();
|
|
69
|
+
}
|
|
70
|
+
if (query) {
|
|
71
|
+
const queryParams = new URLSearchParams(query);
|
|
72
|
+
for (const [key, value] of queryParams.entries()) {
|
|
73
|
+
if (!value)
|
|
74
|
+
continue;
|
|
75
|
+
if (key === "amount") {
|
|
76
|
+
const amount = Number(value);
|
|
77
|
+
if (!isFinite(amount)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (amount < 0) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
params[key] = amount;
|
|
84
|
+
}
|
|
85
|
+
else if (key === "ark") {
|
|
86
|
+
// Validate ARK address format
|
|
87
|
+
if (value.startsWith("ark") || value.startsWith("tark")) {
|
|
88
|
+
params[key] = value;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.warn("Invalid ARK address format");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (key === "sp") {
|
|
95
|
+
// Validate Silent Payment address format (placeholder)
|
|
96
|
+
if (value.startsWith("sp")) {
|
|
97
|
+
params[key] = value;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.warn("Invalid Silent Payment address format");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
params[key] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
originalString: uri,
|
|
110
|
+
params,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.BIP21 = BIP21;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectCoins = selectCoins;
|
|
4
|
+
exports.selectVirtualCoins = selectVirtualCoins;
|
|
5
|
+
/**
|
|
6
|
+
* Select coins to reach a target amount, prioritizing those closer to expiry
|
|
7
|
+
* @param coins List of coins to select from
|
|
8
|
+
* @param targetAmount Target amount to reach in satoshis
|
|
9
|
+
* @returns Selected coins and change amount, or null if insufficient funds
|
|
10
|
+
*/
|
|
11
|
+
function selectCoins(coins, targetAmount) {
|
|
12
|
+
// Sort coins by amount (descending)
|
|
13
|
+
const sortedCoins = [...coins].sort((a, b) => b.value - a.value);
|
|
14
|
+
const selectedCoins = [];
|
|
15
|
+
let selectedAmount = 0;
|
|
16
|
+
// Select coins until we have enough
|
|
17
|
+
for (const coin of sortedCoins) {
|
|
18
|
+
selectedCoins.push(coin);
|
|
19
|
+
selectedAmount += coin.value;
|
|
20
|
+
if (selectedAmount >= targetAmount) {
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Check if we have enough
|
|
25
|
+
if (selectedAmount < targetAmount) {
|
|
26
|
+
return { inputs: null, changeAmount: 0 };
|
|
27
|
+
}
|
|
28
|
+
// Calculate change
|
|
29
|
+
const changeAmount = selectedAmount - targetAmount;
|
|
30
|
+
return {
|
|
31
|
+
inputs: selectedCoins,
|
|
32
|
+
changeAmount,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Select virtual coins to reach a target amount, prioritizing those closer to expiry
|
|
37
|
+
* @param coins List of virtual coins to select from
|
|
38
|
+
* @param targetAmount Target amount to reach in satoshis
|
|
39
|
+
* @returns Selected coins and change amount, or null if insufficient funds
|
|
40
|
+
*/
|
|
41
|
+
function selectVirtualCoins(coins, targetAmount) {
|
|
42
|
+
// Sort VTXOs by expiry (ascending) and amount (descending)
|
|
43
|
+
const sortedCoins = [...coins].sort((a, b) => {
|
|
44
|
+
// First sort by expiry if available
|
|
45
|
+
const expiryA = a.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
|
|
46
|
+
const expiryB = b.virtualStatus.batchExpiry || Number.MAX_SAFE_INTEGER;
|
|
47
|
+
if (expiryA !== expiryB) {
|
|
48
|
+
return expiryA - expiryB; // Earlier expiry first
|
|
49
|
+
}
|
|
50
|
+
// Then sort by amount
|
|
51
|
+
return b.value - a.value; // Larger amount first
|
|
52
|
+
});
|
|
53
|
+
const selectedCoins = [];
|
|
54
|
+
let selectedAmount = 0;
|
|
55
|
+
// Select coins until we have enough
|
|
56
|
+
for (const coin of sortedCoins) {
|
|
57
|
+
selectedCoins.push(coin);
|
|
58
|
+
selectedAmount += coin.value;
|
|
59
|
+
if (selectedAmount >= targetAmount) {
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Check if we have enough
|
|
64
|
+
if (selectedAmount < targetAmount) {
|
|
65
|
+
return { inputs: null, changeAmount: 0 };
|
|
66
|
+
}
|
|
67
|
+
// Calculate change
|
|
68
|
+
const changeAmount = selectedAmount - targetAmount;
|
|
69
|
+
return {
|
|
70
|
+
inputs: selectedCoins,
|
|
71
|
+
changeAmount,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VTXO_TAPROOT_TREE_KEY_PREFIX = exports.CONDITION_WITNESS_KEY_PREFIX = void 0;
|
|
4
|
+
exports.addVtxoTaprootTree = addVtxoTaprootTree;
|
|
5
|
+
exports.addConditionWitness = addConditionWitness;
|
|
6
|
+
exports.createVirtualTx = createVirtualTx;
|
|
7
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
8
|
+
const tapscript_1 = require("../script/tapscript");
|
|
9
|
+
const base_1 = require("../script/base");
|
|
10
|
+
const address_1 = require("../script/address");
|
|
11
|
+
const base_2 = require("@scure/base");
|
|
12
|
+
const ARK_UNKNOWN_KEY_TYPE = 255;
|
|
13
|
+
// Constant for condition witness key prefix
|
|
14
|
+
exports.CONDITION_WITNESS_KEY_PREFIX = new TextEncoder().encode("condition");
|
|
15
|
+
exports.VTXO_TAPROOT_TREE_KEY_PREFIX = new TextEncoder().encode("taptree");
|
|
16
|
+
function addVtxoTaprootTree(inIndex, tx, scripts) {
|
|
17
|
+
tx.updateInput(inIndex, {
|
|
18
|
+
unknown: [
|
|
19
|
+
...(tx.getInput(inIndex)?.unknown ?? []),
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
type: ARK_UNKNOWN_KEY_TYPE,
|
|
23
|
+
key: exports.VTXO_TAPROOT_TREE_KEY_PREFIX,
|
|
24
|
+
},
|
|
25
|
+
encodeTaprootTree(scripts),
|
|
26
|
+
],
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function addConditionWitness(inIndex, tx, witness) {
|
|
31
|
+
const witnessBytes = btc_signer_1.RawWitness.encode(witness);
|
|
32
|
+
tx.updateInput(inIndex, {
|
|
33
|
+
unknown: [
|
|
34
|
+
...(tx.getInput(inIndex)?.unknown ?? []),
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
type: ARK_UNKNOWN_KEY_TYPE,
|
|
38
|
+
key: exports.CONDITION_WITNESS_KEY_PREFIX,
|
|
39
|
+
},
|
|
40
|
+
witnessBytes,
|
|
41
|
+
],
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function createVirtualTx(inputs, outputs) {
|
|
46
|
+
let lockTime;
|
|
47
|
+
for (const input of inputs) {
|
|
48
|
+
const tapscript = (0, tapscript_1.decodeTapscript)((0, base_1.scriptFromTapLeafScript)(input.tapLeafScript));
|
|
49
|
+
if (tapscript_1.CLTVMultisigTapscript.is(tapscript)) {
|
|
50
|
+
lockTime = Number(tapscript.params.absoluteTimelock);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const tx = new btc_signer_1.Transaction({
|
|
54
|
+
allowUnknown: true,
|
|
55
|
+
lockTime,
|
|
56
|
+
});
|
|
57
|
+
for (const [i, input] of inputs.entries()) {
|
|
58
|
+
tx.addInput({
|
|
59
|
+
txid: input.txid,
|
|
60
|
+
index: input.vout,
|
|
61
|
+
sequence: lockTime ? btc_signer_1.DEFAULT_SEQUENCE - 1 : undefined,
|
|
62
|
+
witnessUtxo: {
|
|
63
|
+
script: base_1.VtxoScript.decode(input.scripts).pkScript,
|
|
64
|
+
amount: BigInt(input.value),
|
|
65
|
+
},
|
|
66
|
+
tapLeafScript: [input.tapLeafScript],
|
|
67
|
+
});
|
|
68
|
+
// add BIP371 encoded taproot tree to the unknown key field
|
|
69
|
+
addVtxoTaprootTree(i, tx, input.scripts.map(base_2.hex.decode));
|
|
70
|
+
}
|
|
71
|
+
for (const output of outputs) {
|
|
72
|
+
tx.addOutput({
|
|
73
|
+
amount: output.amount,
|
|
74
|
+
script: address_1.ArkAddress.decode(output.address).pkScript,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return tx;
|
|
78
|
+
}
|
|
79
|
+
function encodeTaprootTree(leaves) {
|
|
80
|
+
const chunks = [];
|
|
81
|
+
// Write number of leaves as compact size uint
|
|
82
|
+
chunks.push(encodeCompactSizeUint(leaves.length));
|
|
83
|
+
for (const tapscript of leaves) {
|
|
84
|
+
// Write depth (always 1 for now)
|
|
85
|
+
chunks.push(new Uint8Array([1]));
|
|
86
|
+
// Write leaf version (0xc0 for tapscript)
|
|
87
|
+
chunks.push(new Uint8Array([0xc0]));
|
|
88
|
+
// Write script length and script
|
|
89
|
+
chunks.push(encodeCompactSizeUint(tapscript.length));
|
|
90
|
+
chunks.push(tapscript);
|
|
91
|
+
}
|
|
92
|
+
// Concatenate all chunks
|
|
93
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
94
|
+
const result = new Uint8Array(totalLength);
|
|
95
|
+
let offset = 0;
|
|
96
|
+
for (const chunk of chunks) {
|
|
97
|
+
result.set(chunk, offset);
|
|
98
|
+
offset += chunk.length;
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function encodeCompactSizeUint(value) {
|
|
103
|
+
if (value < 0xfd) {
|
|
104
|
+
return new Uint8Array([value]);
|
|
105
|
+
}
|
|
106
|
+
else if (value <= 0xffff) {
|
|
107
|
+
const buffer = new Uint8Array(3);
|
|
108
|
+
buffer[0] = 0xfd;
|
|
109
|
+
new DataView(buffer.buffer).setUint16(1, value, true);
|
|
110
|
+
return buffer;
|
|
111
|
+
}
|
|
112
|
+
else if (value <= 0xffffffff) {
|
|
113
|
+
const buffer = new Uint8Array(5);
|
|
114
|
+
buffer[0] = 0xfe;
|
|
115
|
+
new DataView(buffer.buffer).setUint32(1, value, true);
|
|
116
|
+
return buffer;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const buffer = new Uint8Array(9);
|
|
120
|
+
buffer[0] = 0xff;
|
|
121
|
+
new DataView(buffer.buffer).setBigUint64(1, BigInt(value), true);
|
|
122
|
+
return buffer;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.vtxosToTxs = vtxosToTxs;
|
|
4
|
+
const wallet_1 = require("../wallet");
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to find vtxos that were spent in a settlement
|
|
7
|
+
*/
|
|
8
|
+
function findVtxosSpentInSettlement(vtxos, vtxo) {
|
|
9
|
+
if (vtxo.virtualStatus.state === "pending") {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return vtxos.filter((v) => {
|
|
13
|
+
if (!v.spentBy)
|
|
14
|
+
return false;
|
|
15
|
+
return v.spentBy === vtxo.virtualStatus.batchTxID;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Helper function to find vtxos that were spent in a payment
|
|
20
|
+
*/
|
|
21
|
+
function findVtxosSpentInPayment(vtxos, vtxo) {
|
|
22
|
+
return vtxos.filter((v) => {
|
|
23
|
+
if (!v.spentBy)
|
|
24
|
+
return false;
|
|
25
|
+
return v.spentBy === vtxo.txid;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper function to find vtxos that resulted from a spentBy transaction
|
|
30
|
+
*/
|
|
31
|
+
function findVtxosResultedFromSpentBy(vtxos, spentBy) {
|
|
32
|
+
return vtxos.filter((v) => {
|
|
33
|
+
if (v.virtualStatus.state !== "pending" &&
|
|
34
|
+
v.virtualStatus.batchTxID === spentBy) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return v.txid === spentBy;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Helper function to reduce vtxos to their total amount
|
|
42
|
+
*/
|
|
43
|
+
function reduceVtxosAmount(vtxos) {
|
|
44
|
+
return vtxos.reduce((sum, v) => sum + v.value, 0);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Helper function to get a vtxo from a list of vtxos
|
|
48
|
+
*/
|
|
49
|
+
function getVtxo(resultedVtxos, spentVtxos) {
|
|
50
|
+
if (resultedVtxos.length === 0) {
|
|
51
|
+
return spentVtxos[0];
|
|
52
|
+
}
|
|
53
|
+
return resultedVtxos[0];
|
|
54
|
+
}
|
|
55
|
+
function vtxosToTxs(spendable, spent, boardingRounds) {
|
|
56
|
+
const txs = [];
|
|
57
|
+
// Receive case
|
|
58
|
+
// All vtxos are received unless:
|
|
59
|
+
// - they resulted from a settlement (either boarding or refresh)
|
|
60
|
+
// - they are the change of a spend tx
|
|
61
|
+
let vtxosLeftToCheck = [...spent];
|
|
62
|
+
for (const vtxo of [...spendable, ...spent]) {
|
|
63
|
+
if (vtxo.virtualStatus.state !== "pending" &&
|
|
64
|
+
boardingRounds.has(vtxo.virtualStatus.batchTxID || "")) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const settleVtxos = findVtxosSpentInSettlement(vtxosLeftToCheck, vtxo);
|
|
68
|
+
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, settleVtxos);
|
|
69
|
+
const settleAmount = reduceVtxosAmount(settleVtxos);
|
|
70
|
+
if (vtxo.value <= settleAmount) {
|
|
71
|
+
continue; // settlement or change, ignore
|
|
72
|
+
}
|
|
73
|
+
const spentVtxos = findVtxosSpentInPayment(vtxosLeftToCheck, vtxo);
|
|
74
|
+
vtxosLeftToCheck = removeVtxosFromList(vtxosLeftToCheck, spentVtxos);
|
|
75
|
+
const spentAmount = reduceVtxosAmount(spentVtxos);
|
|
76
|
+
if (vtxo.value <= spentAmount) {
|
|
77
|
+
continue; // settlement or change, ignore
|
|
78
|
+
}
|
|
79
|
+
const txKey = {
|
|
80
|
+
roundTxid: vtxo.virtualStatus.batchTxID || "",
|
|
81
|
+
boardingTxid: "",
|
|
82
|
+
redeemTxid: "",
|
|
83
|
+
};
|
|
84
|
+
let settled = vtxo.virtualStatus.state !== "pending";
|
|
85
|
+
if (vtxo.virtualStatus.state === "pending") {
|
|
86
|
+
txKey.redeemTxid = vtxo.txid;
|
|
87
|
+
if (vtxo.spentBy) {
|
|
88
|
+
settled = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
txs.push({
|
|
92
|
+
key: txKey,
|
|
93
|
+
amount: vtxo.value - settleAmount - spentAmount,
|
|
94
|
+
type: wallet_1.TxType.TxReceived,
|
|
95
|
+
createdAt: vtxo.createdAt.getTime(),
|
|
96
|
+
settled,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// send case
|
|
100
|
+
// All "spentBy" vtxos are payments unless:
|
|
101
|
+
// - they are settlements
|
|
102
|
+
// aggregate spent by spentId
|
|
103
|
+
const vtxosBySpentBy = new Map();
|
|
104
|
+
for (const v of spent) {
|
|
105
|
+
if (!v.spentBy)
|
|
106
|
+
continue;
|
|
107
|
+
if (!vtxosBySpentBy.has(v.spentBy)) {
|
|
108
|
+
vtxosBySpentBy.set(v.spentBy, []);
|
|
109
|
+
}
|
|
110
|
+
const currentVtxos = vtxosBySpentBy.get(v.spentBy);
|
|
111
|
+
vtxosBySpentBy.set(v.spentBy, [...currentVtxos, v]);
|
|
112
|
+
}
|
|
113
|
+
for (const [sb, vtxos] of vtxosBySpentBy) {
|
|
114
|
+
const resultedVtxos = findVtxosResultedFromSpentBy([...spendable, ...spent], sb);
|
|
115
|
+
const resultedAmount = reduceVtxosAmount(resultedVtxos);
|
|
116
|
+
const spentAmount = reduceVtxosAmount(vtxos);
|
|
117
|
+
if (spentAmount <= resultedAmount) {
|
|
118
|
+
continue; // settlement or change, ignore
|
|
119
|
+
}
|
|
120
|
+
const vtxo = getVtxo(resultedVtxos, vtxos);
|
|
121
|
+
const txKey = {
|
|
122
|
+
roundTxid: vtxo.virtualStatus.batchTxID || "",
|
|
123
|
+
boardingTxid: "",
|
|
124
|
+
redeemTxid: "",
|
|
125
|
+
};
|
|
126
|
+
if (vtxo.virtualStatus.state === "pending") {
|
|
127
|
+
txKey.redeemTxid = vtxo.txid;
|
|
128
|
+
}
|
|
129
|
+
txs.push({
|
|
130
|
+
key: txKey,
|
|
131
|
+
amount: spentAmount - resultedAmount,
|
|
132
|
+
type: wallet_1.TxType.TxSent,
|
|
133
|
+
createdAt: vtxo.createdAt.getTime(),
|
|
134
|
+
settled: true,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return txs;
|
|
138
|
+
}
|
|
139
|
+
function removeVtxosFromList(vtxos, vtxosToRemove) {
|
|
140
|
+
return vtxos.filter((v) => {
|
|
141
|
+
for (const vtxoToRemove of vtxosToRemove) {
|
|
142
|
+
if (v.txid === vtxoToRemove.txid && v.vout === vtxoToRemove.vout) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TxWeightEstimator = void 0;
|
|
4
|
+
class TxWeightEstimator {
|
|
5
|
+
constructor(hasWitness, inputCount, outputCount, inputSize, inputWitnessSize, outputSize) {
|
|
6
|
+
this.hasWitness = hasWitness;
|
|
7
|
+
this.inputCount = inputCount;
|
|
8
|
+
this.outputCount = outputCount;
|
|
9
|
+
this.inputSize = inputSize;
|
|
10
|
+
this.inputWitnessSize = inputWitnessSize;
|
|
11
|
+
this.outputSize = outputSize;
|
|
12
|
+
}
|
|
13
|
+
static create() {
|
|
14
|
+
return new TxWeightEstimator(false, 0, 0, 0, 0, 0);
|
|
15
|
+
}
|
|
16
|
+
addKeySpendInput(isDefault = true) {
|
|
17
|
+
this.inputCount++;
|
|
18
|
+
this.inputWitnessSize += 64 + 1 + (isDefault ? 0 : 1);
|
|
19
|
+
this.inputSize += TxWeightEstimator.INPUT_SIZE;
|
|
20
|
+
this.hasWitness = true;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
addP2PKHInput() {
|
|
24
|
+
this.inputCount++;
|
|
25
|
+
this.inputWitnessSize++;
|
|
26
|
+
this.inputSize +=
|
|
27
|
+
TxWeightEstimator.INPUT_SIZE +
|
|
28
|
+
TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
addTapscriptInput(leafWitnessSize, leafScriptSize, leafControlBlockSize) {
|
|
32
|
+
const controlBlockWitnessSize = 1 +
|
|
33
|
+
TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE +
|
|
34
|
+
1 +
|
|
35
|
+
leafScriptSize +
|
|
36
|
+
1 +
|
|
37
|
+
leafControlBlockSize;
|
|
38
|
+
this.inputCount++;
|
|
39
|
+
this.inputWitnessSize += leafWitnessSize + controlBlockWitnessSize;
|
|
40
|
+
this.inputSize += TxWeightEstimator.INPUT_SIZE;
|
|
41
|
+
this.hasWitness = true;
|
|
42
|
+
this.inputCount++;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
addP2WKHOutput() {
|
|
46
|
+
this.outputCount++;
|
|
47
|
+
this.outputSize +=
|
|
48
|
+
TxWeightEstimator.OUTPUT_SIZE + TxWeightEstimator.P2WKH_OUTPUT_SIZE;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
vsize() {
|
|
52
|
+
const getVarIntSize = (n) => {
|
|
53
|
+
if (n < 0xfd)
|
|
54
|
+
return 1;
|
|
55
|
+
if (n < 0xffff)
|
|
56
|
+
return 3;
|
|
57
|
+
if (n < 0xffffffff)
|
|
58
|
+
return 5;
|
|
59
|
+
return 9;
|
|
60
|
+
};
|
|
61
|
+
const inputCount = getVarIntSize(this.inputCount);
|
|
62
|
+
const outputCount = getVarIntSize(this.outputCount);
|
|
63
|
+
// Calculate the size of the transaction without witness data
|
|
64
|
+
const txSizeStripped = TxWeightEstimator.BASE_TX_SIZE +
|
|
65
|
+
inputCount +
|
|
66
|
+
this.inputSize +
|
|
67
|
+
outputCount +
|
|
68
|
+
this.outputSize;
|
|
69
|
+
// Calculate the total weight
|
|
70
|
+
let weight = txSizeStripped * TxWeightEstimator.WITNESS_SCALE_FACTOR;
|
|
71
|
+
// Add witness data if present
|
|
72
|
+
if (this.hasWitness) {
|
|
73
|
+
weight +=
|
|
74
|
+
TxWeightEstimator.WITNESS_HEADER_SIZE + this.inputWitnessSize;
|
|
75
|
+
}
|
|
76
|
+
// Convert weight to vsize (weight / 4, rounded up)
|
|
77
|
+
return vsize(weight);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.TxWeightEstimator = TxWeightEstimator;
|
|
81
|
+
TxWeightEstimator.P2PKH_SCRIPT_SIG_SIZE = 1 + 73 + 1 + 33;
|
|
82
|
+
TxWeightEstimator.INPUT_SIZE = 32 + 4 + 1 + 4;
|
|
83
|
+
TxWeightEstimator.BASE_CONTROL_BLOCK_SIZE = 1 + 32;
|
|
84
|
+
TxWeightEstimator.OUTPUT_SIZE = 8 + 1;
|
|
85
|
+
TxWeightEstimator.P2WKH_OUTPUT_SIZE = 1 + 1 + 20;
|
|
86
|
+
TxWeightEstimator.BASE_TX_SIZE = 8 + 2; // Version + LockTime
|
|
87
|
+
TxWeightEstimator.WITNESS_HEADER_SIZE = 2; // Flag + Marker
|
|
88
|
+
TxWeightEstimator.WITNESS_SCALE_FACTOR = 4;
|
|
89
|
+
const vsize = (weight) => {
|
|
90
|
+
const value = BigInt(Math.ceil(weight / TxWeightEstimator.WITNESS_SCALE_FACTOR));
|
|
91
|
+
return {
|
|
92
|
+
value,
|
|
93
|
+
fee: (feeRate) => feeRate * value,
|
|
94
|
+
};
|
|
95
|
+
};
|