@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,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PartialSig = exports.PartialSignatureError = void 0;
|
|
37
|
+
exports.sign = sign;
|
|
38
|
+
const musig = __importStar(require("@scure/btc-signer/musig2"));
|
|
39
|
+
const utils_1 = require("@noble/curves/abstract/utils");
|
|
40
|
+
const secp256k1_1 = require("@noble/secp256k1");
|
|
41
|
+
const keys_1 = require("./keys");
|
|
42
|
+
const secp256k1_2 = require("@noble/curves/secp256k1");
|
|
43
|
+
// Add this error type for decode failures
|
|
44
|
+
class PartialSignatureError extends Error {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "PartialSignatureError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.PartialSignatureError = PartialSignatureError;
|
|
51
|
+
// Implement a concrete class for PartialSignature
|
|
52
|
+
class PartialSig {
|
|
53
|
+
constructor(s, R) {
|
|
54
|
+
this.s = s;
|
|
55
|
+
this.R = R;
|
|
56
|
+
if (s.length !== 32) {
|
|
57
|
+
throw new PartialSignatureError("Invalid s length");
|
|
58
|
+
}
|
|
59
|
+
if (R.length !== 33) {
|
|
60
|
+
throw new PartialSignatureError("Invalid R length");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Encodes the partial signature into bytes
|
|
65
|
+
* Returns a 32-byte array containing just the s value
|
|
66
|
+
*/
|
|
67
|
+
encode() {
|
|
68
|
+
// Return copy of s bytes
|
|
69
|
+
return new Uint8Array(this.s);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Decodes a partial signature from bytes
|
|
73
|
+
* @param bytes - 32-byte array containing s value
|
|
74
|
+
*/
|
|
75
|
+
static decode(bytes) {
|
|
76
|
+
if (bytes.length !== 32) {
|
|
77
|
+
throw new PartialSignatureError("Invalid partial signature length");
|
|
78
|
+
}
|
|
79
|
+
// Verify s is less than curve order
|
|
80
|
+
const s = (0, utils_1.bytesToNumberBE)(bytes);
|
|
81
|
+
if (s >= secp256k1_1.CURVE.n) {
|
|
82
|
+
throw new PartialSignatureError("s value overflows curve order");
|
|
83
|
+
}
|
|
84
|
+
// For decode we don't have R, so we'll need to compute it later
|
|
85
|
+
const R = new Uint8Array(33); // Zero R for now
|
|
86
|
+
return new PartialSig(bytes, R);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.PartialSig = PartialSig;
|
|
90
|
+
/**
|
|
91
|
+
* Generates a MuSig2 partial signature
|
|
92
|
+
*/
|
|
93
|
+
function sign(secNonce, privateKey, combinedNonce, publicKeys, message, options) {
|
|
94
|
+
let tweakBytes;
|
|
95
|
+
if (options?.taprootTweak !== undefined) {
|
|
96
|
+
const { preTweakedKey } = (0, keys_1.aggregateKeys)(options?.sortKeys ? musig.sortKeys(publicKeys) : publicKeys, true);
|
|
97
|
+
tweakBytes = secp256k1_2.schnorr.utils.taggedHash("TapTweak", preTweakedKey.subarray(1), options.taprootTweak);
|
|
98
|
+
}
|
|
99
|
+
const session = new musig.Session(combinedNonce, options?.sortKeys ? musig.sortKeys(publicKeys) : publicKeys, message, tweakBytes ? [tweakBytes] : undefined, tweakBytes ? [true] : undefined);
|
|
100
|
+
const partialSig = session.sign(secNonce, privateKey);
|
|
101
|
+
return PartialSig.decode(partialSig);
|
|
102
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.networks = exports.getNetwork = void 0;
|
|
4
|
+
const btc_signer_1 = require("@scure/btc-signer");
|
|
5
|
+
const getNetwork = (network) => {
|
|
6
|
+
return exports.networks[network];
|
|
7
|
+
};
|
|
8
|
+
exports.getNetwork = getNetwork;
|
|
9
|
+
exports.networks = {
|
|
10
|
+
bitcoin: withArkPrefix(btc_signer_1.NETWORK, "ark"),
|
|
11
|
+
testnet: withArkPrefix(btc_signer_1.TEST_NETWORK, "tark"),
|
|
12
|
+
signet: withArkPrefix(btc_signer_1.TEST_NETWORK, "tark"),
|
|
13
|
+
mutinynet: withArkPrefix(btc_signer_1.TEST_NETWORK, "tark"),
|
|
14
|
+
regtest: withArkPrefix({
|
|
15
|
+
...btc_signer_1.TEST_NETWORK,
|
|
16
|
+
bech32: "bcrt",
|
|
17
|
+
pubKeyHash: 0x6f,
|
|
18
|
+
scriptHash: 0xc4,
|
|
19
|
+
}, "tark"),
|
|
20
|
+
};
|
|
21
|
+
function withArkPrefix(network, prefix) {
|
|
22
|
+
return {
|
|
23
|
+
...network,
|
|
24
|
+
hrp: prefix,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RestArkProvider = exports.SettlementEventType = void 0;
|
|
4
|
+
const vtxoTree_1 = require("../tree/vtxoTree");
|
|
5
|
+
const base_1 = require("@scure/base");
|
|
6
|
+
var SettlementEventType;
|
|
7
|
+
(function (SettlementEventType) {
|
|
8
|
+
SettlementEventType["Finalization"] = "finalization";
|
|
9
|
+
SettlementEventType["Finalized"] = "finalized";
|
|
10
|
+
SettlementEventType["Failed"] = "failed";
|
|
11
|
+
SettlementEventType["SigningStart"] = "signing_start";
|
|
12
|
+
SettlementEventType["SigningNoncesGenerated"] = "signing_nonces_generated";
|
|
13
|
+
})(SettlementEventType || (exports.SettlementEventType = SettlementEventType = {}));
|
|
14
|
+
class RestArkProvider {
|
|
15
|
+
constructor(serverUrl) {
|
|
16
|
+
this.serverUrl = serverUrl;
|
|
17
|
+
}
|
|
18
|
+
async getInfo() {
|
|
19
|
+
const url = `${this.serverUrl}/v1/info`;
|
|
20
|
+
const response = await fetch(url);
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw new Error(`Failed to get server info: ${response.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
const fromServer = await response.json();
|
|
25
|
+
return {
|
|
26
|
+
...fromServer,
|
|
27
|
+
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
28
|
+
batchExpiry: BigInt(fromServer.vtxoTreeExpiry ?? 0),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async getVirtualCoins(address) {
|
|
32
|
+
const url = `${this.serverUrl}/v1/vtxos/${address}`;
|
|
33
|
+
const response = await fetch(url);
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Failed to fetch VTXOs: ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
return {
|
|
39
|
+
spendableVtxos: [...(data.spendableVtxos || [])].map(convertVtxo),
|
|
40
|
+
spentVtxos: [...(data.spentVtxos || [])].map(convertVtxo),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async submitVirtualTx(psbtBase64) {
|
|
44
|
+
const url = `${this.serverUrl}/v1/redeem-tx`;
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
redeem_tx: psbtBase64,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorText = await response.text();
|
|
56
|
+
try {
|
|
57
|
+
const grpcError = JSON.parse(errorText);
|
|
58
|
+
// gRPC errors usually have a message and code field
|
|
59
|
+
throw new Error(`Failed to submit virtual transaction: ${grpcError.message || grpcError.error || errorText}`);
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
61
|
+
}
|
|
62
|
+
catch (_) {
|
|
63
|
+
// If JSON parse fails, use the raw error text
|
|
64
|
+
throw new Error(`Failed to submit virtual transaction: ${errorText}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
// Handle both current and future response formats
|
|
69
|
+
return data.txid || data.signedRedeemTx;
|
|
70
|
+
}
|
|
71
|
+
async subscribeToEvents(callback) {
|
|
72
|
+
const url = `${this.serverUrl}/v1/events`;
|
|
73
|
+
let abortController = new AbortController();
|
|
74
|
+
(async () => {
|
|
75
|
+
while (!abortController.signal.aborted) {
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: "application/json",
|
|
80
|
+
},
|
|
81
|
+
signal: abortController.signal,
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Unexpected status ${response.status} when fetching event stream`);
|
|
85
|
+
}
|
|
86
|
+
if (!response.body) {
|
|
87
|
+
throw new Error("Response body is null");
|
|
88
|
+
}
|
|
89
|
+
const reader = response.body.getReader();
|
|
90
|
+
const decoder = new TextDecoder();
|
|
91
|
+
let buffer = "";
|
|
92
|
+
while (!abortController.signal.aborted) {
|
|
93
|
+
const { done, value } = await reader.read();
|
|
94
|
+
if (done)
|
|
95
|
+
break;
|
|
96
|
+
// Append new data to buffer and split by newlines
|
|
97
|
+
buffer += decoder.decode(value, { stream: true });
|
|
98
|
+
const lines = buffer.split("\n");
|
|
99
|
+
// Process all complete lines
|
|
100
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
101
|
+
const line = lines[i].trim();
|
|
102
|
+
if (!line)
|
|
103
|
+
continue;
|
|
104
|
+
try {
|
|
105
|
+
const data = JSON.parse(line);
|
|
106
|
+
callback(data);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error("Failed to parse event:", err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Keep the last partial line in the buffer
|
|
113
|
+
buffer = lines[lines.length - 1];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (!abortController.signal.aborted) {
|
|
118
|
+
console.error("Event stream error:", error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
// Return unsubscribe function
|
|
124
|
+
return () => {
|
|
125
|
+
abortController.abort();
|
|
126
|
+
// Create a new controller for potential future subscriptions
|
|
127
|
+
abortController = new AbortController();
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async registerInputsForNextRound(inputs) {
|
|
131
|
+
const url = `${this.serverUrl}/v1/round/registerInputs`;
|
|
132
|
+
const vtxoInputs = [];
|
|
133
|
+
const noteInputs = [];
|
|
134
|
+
for (const input of inputs) {
|
|
135
|
+
if (typeof input === "string") {
|
|
136
|
+
noteInputs.push(input);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
vtxoInputs.push({
|
|
140
|
+
outpoint: {
|
|
141
|
+
txid: input.outpoint.txid,
|
|
142
|
+
vout: input.outpoint.vout,
|
|
143
|
+
},
|
|
144
|
+
tapscripts: {
|
|
145
|
+
scripts: input.tapscripts,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const response = await fetch(url, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "application/json",
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
inputs: vtxoInputs,
|
|
157
|
+
notes: noteInputs,
|
|
158
|
+
}),
|
|
159
|
+
});
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
const errorText = await response.text();
|
|
162
|
+
throw new Error(`Failed to register inputs: ${errorText}`);
|
|
163
|
+
}
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
return { requestId: data.requestId };
|
|
166
|
+
}
|
|
167
|
+
async registerOutputsForNextRound(requestId, outputs, cosignersPublicKeys, signingAll = false) {
|
|
168
|
+
const url = `${this.serverUrl}/v1/round/registerOutputs`;
|
|
169
|
+
const response = await fetch(url, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
requestId,
|
|
176
|
+
outputs: outputs.map((output) => ({
|
|
177
|
+
address: output.address,
|
|
178
|
+
amount: output.amount.toString(10),
|
|
179
|
+
})),
|
|
180
|
+
musig2: {
|
|
181
|
+
cosignersPublicKeys,
|
|
182
|
+
signingAll,
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
const errorText = await response.text();
|
|
188
|
+
throw new Error(`Failed to register outputs: ${errorText}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async submitTreeNonces(settlementID, pubkey, nonces) {
|
|
192
|
+
const url = `${this.serverUrl}/v1/round/tree/submitNonces`;
|
|
193
|
+
const response = await fetch(url, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: {
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
roundId: settlementID,
|
|
200
|
+
pubkey,
|
|
201
|
+
treeNonces: encodeNoncesMatrix(nonces),
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
const errorText = await response.text();
|
|
206
|
+
throw new Error(`Failed to submit tree nonces: ${errorText}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async submitTreeSignatures(settlementID, pubkey, signatures) {
|
|
210
|
+
const url = `${this.serverUrl}/v1/round/tree/submitSignatures`;
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
roundId: settlementID,
|
|
218
|
+
pubkey,
|
|
219
|
+
treeSignatures: encodeSignaturesMatrix(signatures),
|
|
220
|
+
}),
|
|
221
|
+
});
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
const errorText = await response.text();
|
|
224
|
+
throw new Error(`Failed to submit tree signatures: ${errorText}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async submitSignedForfeitTxs(signedForfeitTxs, signedRoundTx) {
|
|
228
|
+
const url = `${this.serverUrl}/v1/round/submitForfeitTxs`;
|
|
229
|
+
const response = await fetch(url, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
signedForfeitTxs: signedForfeitTxs,
|
|
236
|
+
signedRoundTx: signedRoundTx,
|
|
237
|
+
}),
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`Failed to submit forfeit transactions: ${response.statusText}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async ping(requestId) {
|
|
244
|
+
const url = `${this.serverUrl}/v1/round/ping/${requestId}`;
|
|
245
|
+
const response = await fetch(url);
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
throw new Error(`Ping failed: ${response.statusText}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async *getEventStream(signal) {
|
|
251
|
+
const url = `${this.serverUrl}/v1/events`;
|
|
252
|
+
while (!signal?.aborted) {
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(url, {
|
|
255
|
+
headers: {
|
|
256
|
+
Accept: "application/json",
|
|
257
|
+
},
|
|
258
|
+
signal,
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
throw new Error(`Unexpected status ${response.status} when fetching event stream`);
|
|
262
|
+
}
|
|
263
|
+
if (!response.body) {
|
|
264
|
+
throw new Error("Response body is null");
|
|
265
|
+
}
|
|
266
|
+
const reader = response.body.getReader();
|
|
267
|
+
const decoder = new TextDecoder();
|
|
268
|
+
let buffer = "";
|
|
269
|
+
while (!signal?.aborted) {
|
|
270
|
+
const { done, value } = await reader.read();
|
|
271
|
+
if (done) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
// Append new data to buffer and split by newlines
|
|
275
|
+
buffer += decoder.decode(value, { stream: true });
|
|
276
|
+
const lines = buffer.split("\n");
|
|
277
|
+
// Process all complete lines
|
|
278
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
279
|
+
const line = lines[i].trim();
|
|
280
|
+
if (!line)
|
|
281
|
+
continue;
|
|
282
|
+
try {
|
|
283
|
+
const data = JSON.parse(line);
|
|
284
|
+
const event = this.parseSettlementEvent(data.result);
|
|
285
|
+
if (event) {
|
|
286
|
+
yield event;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
console.error("Failed to parse event:", err);
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Keep the last partial line in the buffer
|
|
295
|
+
buffer = lines[lines.length - 1];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
console.error("Event stream error:", error);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async *subscribeForAddress(address, abortSignal) {
|
|
308
|
+
const url = `${this.serverUrl}/v1/vtxos/${address}/subscribe`;
|
|
309
|
+
while (!abortSignal.aborted) {
|
|
310
|
+
try {
|
|
311
|
+
const response = await fetch(url, {
|
|
312
|
+
headers: {
|
|
313
|
+
Accept: "application/json",
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
throw new Error(`Unexpected status ${response.status} when subscribing to address updates`);
|
|
318
|
+
}
|
|
319
|
+
if (!response.body) {
|
|
320
|
+
throw new Error("Response body is null");
|
|
321
|
+
}
|
|
322
|
+
const reader = response.body.getReader();
|
|
323
|
+
const decoder = new TextDecoder();
|
|
324
|
+
let buffer = "";
|
|
325
|
+
while (!abortSignal.aborted) {
|
|
326
|
+
const { done, value } = await reader.read();
|
|
327
|
+
if (done) {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
buffer += decoder.decode(value, { stream: true });
|
|
331
|
+
const lines = buffer.split("\n");
|
|
332
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
333
|
+
const line = lines[i].trim();
|
|
334
|
+
if (!line)
|
|
335
|
+
continue;
|
|
336
|
+
try {
|
|
337
|
+
const data = JSON.parse(line);
|
|
338
|
+
if ("result" in data) {
|
|
339
|
+
yield {
|
|
340
|
+
newVtxos: (data.result.newVtxos || []).map(convertVtxo),
|
|
341
|
+
spentVtxos: (data.result.spentVtxos || []).map(convertVtxo),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
console.error("Failed to parse address update:", err);
|
|
347
|
+
throw err;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
buffer = lines[lines.length - 1];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.error("Address subscription error:", error);
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
toConnectorsIndex(connectorsIndex) {
|
|
360
|
+
return new Map(Object.entries(connectorsIndex).map(([key, value]) => [
|
|
361
|
+
key,
|
|
362
|
+
{ txid: value.txid, vout: value.vout },
|
|
363
|
+
]));
|
|
364
|
+
}
|
|
365
|
+
toTxTree(t) {
|
|
366
|
+
// collect the parent txids to determine later if a node is a leaf
|
|
367
|
+
const parentTxids = new Set();
|
|
368
|
+
t.levels.forEach((level) => level.nodes.forEach((node) => {
|
|
369
|
+
if (node.parentTxid) {
|
|
370
|
+
parentTxids.add(node.parentTxid);
|
|
371
|
+
}
|
|
372
|
+
}));
|
|
373
|
+
return new vtxoTree_1.TxTree(t.levels.map((level) => level.nodes.map((node) => ({
|
|
374
|
+
txid: node.txid,
|
|
375
|
+
tx: node.tx,
|
|
376
|
+
parentTxid: node.parentTxid,
|
|
377
|
+
leaf: !parentTxids.has(node.txid),
|
|
378
|
+
}))));
|
|
379
|
+
}
|
|
380
|
+
parseSettlementEvent(data) {
|
|
381
|
+
// Check for Finalization event
|
|
382
|
+
if (data.roundFinalization) {
|
|
383
|
+
return {
|
|
384
|
+
type: SettlementEventType.Finalization,
|
|
385
|
+
id: data.roundFinalization.id,
|
|
386
|
+
roundTx: data.roundFinalization.roundTx,
|
|
387
|
+
vtxoTree: this.toTxTree(data.roundFinalization.vtxoTree),
|
|
388
|
+
connectors: this.toTxTree(data.roundFinalization.connectors),
|
|
389
|
+
connectorsIndex: this.toConnectorsIndex(data.roundFinalization.connectorsIndex),
|
|
390
|
+
// divide by 1000 to convert to sat/vbyte
|
|
391
|
+
minRelayFeeRate: BigInt(data.roundFinalization.minRelayFeeRate) /
|
|
392
|
+
BigInt(1000),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
// Check for Finalized event
|
|
396
|
+
if (data.roundFinalized) {
|
|
397
|
+
return {
|
|
398
|
+
type: SettlementEventType.Finalized,
|
|
399
|
+
id: data.roundFinalized.id,
|
|
400
|
+
roundTxid: data.roundFinalized.roundTxid,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
// Check for Failed event
|
|
404
|
+
if (data.roundFailed) {
|
|
405
|
+
return {
|
|
406
|
+
type: SettlementEventType.Failed,
|
|
407
|
+
id: data.roundFailed.id,
|
|
408
|
+
reason: data.roundFailed.reason,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
// Check for Signing event
|
|
412
|
+
if (data.roundSigning) {
|
|
413
|
+
return {
|
|
414
|
+
type: SettlementEventType.SigningStart,
|
|
415
|
+
id: data.roundSigning.id,
|
|
416
|
+
cosignersPublicKeys: data.roundSigning.cosignersPubkeys,
|
|
417
|
+
unsignedVtxoTree: this.toTxTree(data.roundSigning.unsignedVtxoTree),
|
|
418
|
+
unsignedSettlementTx: data.roundSigning.unsignedRoundTx,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// Check for SigningNoncesGenerated event
|
|
422
|
+
if (data.roundSigningNoncesGenerated) {
|
|
423
|
+
return {
|
|
424
|
+
type: SettlementEventType.SigningNoncesGenerated,
|
|
425
|
+
id: data.roundSigningNoncesGenerated.id,
|
|
426
|
+
treeNonces: decodeNoncesMatrix(base_1.hex.decode(data.roundSigningNoncesGenerated.treeNonces)),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
console.warn("Unknown event structure:", data);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.RestArkProvider = RestArkProvider;
|
|
434
|
+
function encodeMatrix(matrix) {
|
|
435
|
+
// Calculate total size needed:
|
|
436
|
+
// 4 bytes for number of rows
|
|
437
|
+
// For each row: 4 bytes for length + sum of encoded cell lengths + isNil byte * cell count
|
|
438
|
+
let totalSize = 4;
|
|
439
|
+
for (const row of matrix) {
|
|
440
|
+
totalSize += 4; // row length
|
|
441
|
+
for (const cell of row) {
|
|
442
|
+
totalSize += 1;
|
|
443
|
+
totalSize += cell.length;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Create buffer and DataView
|
|
447
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
448
|
+
const view = new DataView(buffer);
|
|
449
|
+
let offset = 0;
|
|
450
|
+
// Write number of rows
|
|
451
|
+
view.setUint32(offset, matrix.length, true); // true for little-endian
|
|
452
|
+
offset += 4;
|
|
453
|
+
// Write each row
|
|
454
|
+
for (const row of matrix) {
|
|
455
|
+
// Write row length
|
|
456
|
+
view.setUint32(offset, row.length, true);
|
|
457
|
+
offset += 4;
|
|
458
|
+
// Write each cell
|
|
459
|
+
for (const cell of row) {
|
|
460
|
+
const notNil = cell.length > 0;
|
|
461
|
+
view.setInt8(offset, notNil ? 1 : 0);
|
|
462
|
+
offset += 1;
|
|
463
|
+
if (!notNil) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
new Uint8Array(buffer).set(cell, offset);
|
|
467
|
+
offset += cell.length;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return new Uint8Array(buffer);
|
|
471
|
+
}
|
|
472
|
+
function decodeMatrix(matrix, cellLength) {
|
|
473
|
+
// Create DataView to read the buffer
|
|
474
|
+
const view = new DataView(matrix.buffer, matrix.byteOffset, matrix.byteLength);
|
|
475
|
+
let offset = 0;
|
|
476
|
+
// Read number of rows
|
|
477
|
+
const numRows = view.getUint32(offset, true); // true for little-endian
|
|
478
|
+
offset += 4;
|
|
479
|
+
// Initialize result matrix
|
|
480
|
+
const result = [];
|
|
481
|
+
// Read each row
|
|
482
|
+
for (let i = 0; i < numRows; i++) {
|
|
483
|
+
// Read row length
|
|
484
|
+
const rowLength = view.getUint32(offset, true);
|
|
485
|
+
offset += 4;
|
|
486
|
+
const row = [];
|
|
487
|
+
// Read each cell in the row
|
|
488
|
+
for (let j = 0; j < rowLength; j++) {
|
|
489
|
+
const notNil = view.getUint8(offset) === 1;
|
|
490
|
+
offset += 1;
|
|
491
|
+
if (notNil) {
|
|
492
|
+
const cell = new Uint8Array(matrix.buffer, matrix.byteOffset + offset, cellLength);
|
|
493
|
+
row.push(new Uint8Array(cell));
|
|
494
|
+
offset += cellLength;
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
row.push(new Uint8Array());
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
result.push(row);
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
function decodeNoncesMatrix(matrix) {
|
|
505
|
+
const decoded = decodeMatrix(matrix, 66);
|
|
506
|
+
return decoded.map((row) => row.map((nonce) => ({ pubNonce: nonce })));
|
|
507
|
+
}
|
|
508
|
+
function encodeNoncesMatrix(nonces) {
|
|
509
|
+
return base_1.hex.encode(encodeMatrix(nonces.map((row) => row.map((nonce) => (nonce ? nonce.pubNonce : new Uint8Array())))));
|
|
510
|
+
}
|
|
511
|
+
function encodeSignaturesMatrix(signatures) {
|
|
512
|
+
return base_1.hex.encode(encodeMatrix(signatures.map((row) => row.map((s) => (s ? s.encode() : new Uint8Array())))));
|
|
513
|
+
}
|
|
514
|
+
function convertVtxo(vtxo) {
|
|
515
|
+
return {
|
|
516
|
+
txid: vtxo.outpoint.txid,
|
|
517
|
+
vout: vtxo.outpoint.vout,
|
|
518
|
+
value: Number(vtxo.amount),
|
|
519
|
+
status: {
|
|
520
|
+
confirmed: !!vtxo.roundTxid,
|
|
521
|
+
},
|
|
522
|
+
virtualStatus: {
|
|
523
|
+
state: vtxo.isPending ? "pending" : "settled",
|
|
524
|
+
batchTxID: vtxo.roundTxid,
|
|
525
|
+
batchExpiry: vtxo.expireAt ? Number(vtxo.expireAt) : undefined,
|
|
526
|
+
},
|
|
527
|
+
spentBy: vtxo.spentBy,
|
|
528
|
+
createdAt: new Date(vtxo.createdAt * 1000),
|
|
529
|
+
};
|
|
530
|
+
}
|