@arkade-os/sdk 0.4.53 → 0.4.55
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/adapters/expo.cjs +5 -5
- package/dist/adapters/expo.d.cts +2 -2
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +3 -3
- package/dist/adapters/indexedDB.cjs +5 -5
- package/dist/adapters/indexedDB.js +4 -4
- package/dist/{ark-B69ugeJw.d.cts → ark-Do9Lqfsp.d.cts} +36 -2
- package/dist/{ark-B69ugeJw.d.ts → ark-Do9Lqfsp.d.ts} +36 -2
- package/dist/{asyncStorageTaskQueue-B0SUQMXw.d.cts → asyncStorageTaskQueue-C3DYxvAT.d.cts} +1 -1
- package/dist/{asyncStorageTaskQueue-DWqxuIAs.d.ts → asyncStorageTaskQueue-DOnBOEDn.d.ts} +1 -1
- package/dist/{chunk-CZTQLA55.cjs → chunk-C6UYPHTQ.cjs} +9 -9
- package/dist/{chunk-CZTQLA55.cjs.map → chunk-C6UYPHTQ.cjs.map} +1 -1
- package/dist/{chunk-4GB73TRR.cjs → chunk-CSS74VCN.cjs} +17 -17
- package/dist/{chunk-4GB73TRR.cjs.map → chunk-CSS74VCN.cjs.map} +1 -1
- package/dist/{chunk-LHYNNDY6.js → chunk-ENWR2F2P.js} +8 -1327
- package/dist/chunk-ENWR2F2P.js.map +1 -0
- package/dist/{chunk-RNMLFY2V.cjs → chunk-G7DOBFQN.cjs} +19 -1367
- package/dist/chunk-G7DOBFQN.cjs.map +1 -0
- package/dist/chunk-JCJ5WFS3.js +2326 -0
- package/dist/chunk-JCJ5WFS3.js.map +1 -0
- package/dist/{chunk-B4WZOJAI.js → chunk-LAONNMM5.js} +4 -4
- package/dist/{chunk-B4WZOJAI.js.map → chunk-LAONNMM5.js.map} +1 -1
- package/dist/chunk-OWCPXRGP.cjs +2415 -0
- package/dist/chunk-OWCPXRGP.cjs.map +1 -0
- package/dist/{chunk-UZLQUWYP.js → chunk-QMPNPZ2T.js} +23 -4
- package/dist/chunk-QMPNPZ2T.js.map +1 -0
- package/dist/{chunk-3MMUKLCI.js → chunk-QO3IKBYL.js} +827 -130
- package/dist/chunk-QO3IKBYL.js.map +1 -0
- package/dist/{chunk-3XWDMLOI.js → chunk-SG4X7SEC.js} +4 -4
- package/dist/{chunk-3XWDMLOI.js.map → chunk-SG4X7SEC.js.map} +1 -1
- package/dist/{chunk-IEMPH7J6.cjs → chunk-V2TOHHPW.cjs} +60 -40
- package/dist/chunk-V2TOHHPW.cjs.map +1 -0
- package/dist/{chunk-3YC3LQ2E.cjs → chunk-ZPL34T2Q.cjs} +1191 -471
- package/dist/chunk-ZPL34T2Q.cjs.map +1 -0
- package/dist/contracts/handlers/index.cjs +8 -8
- package/dist/contracts/handlers/index.d.cts +3 -3
- package/dist/contracts/handlers/index.d.ts +3 -3
- package/dist/contracts/handlers/index.js +2 -2
- package/dist/{delegate-C_Y2YrXC.d.cts → delegate-C0iIBxen.d.cts} +1 -1
- package/dist/{delegate-BU-Cpyjw.d.ts → delegate-DmhFrtdq.d.ts} +1 -1
- package/dist/{index-BWF-C9eh.d.ts → index-BNt4wcSh.d.ts} +2 -2
- package/dist/{index-tzAInyiX.d.cts → index-BnLM1XxA.d.cts} +2 -2
- package/dist/index.cjs +348 -240
- package/dist/index.d.cts +424 -14
- package/dist/index.d.ts +424 -14
- package/dist/index.js +4 -4
- package/dist/repositories/realm/index.cjs +19 -19
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/realm/index.js +5 -5
- package/dist/repositories/sqlite/index.cjs +18 -18
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/repositories/sqlite/index.js +5 -5
- package/dist/{taskRunner-nzJFH8qb.d.ts → taskRunner-Bgs-0I3x.d.ts} +1 -1
- package/dist/{taskRunner-B7svzMhT.d.cts → taskRunner-RwUvgQ5M.d.cts} +1 -1
- package/dist/wallet/expo/background.cjs +14 -14
- package/dist/wallet/expo/background.d.cts +3 -3
- package/dist/wallet/expo/background.d.ts +3 -3
- package/dist/wallet/expo/background.js +6 -6
- package/dist/wallet/expo/index.cjs +14 -14
- package/dist/wallet/expo/index.d.cts +5 -5
- package/dist/wallet/expo/index.d.ts +5 -5
- package/dist/wallet/expo/index.js +5 -5
- package/dist/{wallet-B6Xiferv.d.cts → wallet-5Aou-V5n.d.ts} +83 -6
- package/dist/{wallet-CuHAI-eW.d.ts → wallet-Ck7BH8zR.d.cts} +83 -6
- package/dist/worker/expo/index.cjs +9 -9
- package/dist/worker/expo/index.d.cts +4 -4
- package/dist/worker/expo/index.d.ts +4 -4
- package/dist/worker/expo/index.js +5 -5
- package/package.json +1 -1
- package/dist/chunk-3MMUKLCI.js.map +0 -1
- package/dist/chunk-3YC3LQ2E.cjs.map +0 -1
- package/dist/chunk-E7HB3GKK.cjs +0 -1030
- package/dist/chunk-E7HB3GKK.cjs.map +0 -1
- package/dist/chunk-EFNLTS6Q.js +0 -976
- package/dist/chunk-EFNLTS6Q.js.map +0 -1
- package/dist/chunk-IEMPH7J6.cjs.map +0 -1
- package/dist/chunk-LHYNNDY6.js.map +0 -1
- package/dist/chunk-RNMLFY2V.cjs.map +0 -1
- package/dist/chunk-UZLQUWYP.js.map +0 -1
|
@@ -0,0 +1,2415 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var btcSigner = require('@scure/btc-signer');
|
|
4
|
+
var bip68 = require('bip68');
|
|
5
|
+
var utils_js = require('@scure/btc-signer/utils.js');
|
|
6
|
+
var base = require('@scure/base');
|
|
7
|
+
var script_js = require('@scure/btc-signer/script.js');
|
|
8
|
+
var payment_js = require('@scure/btc-signer/payment.js');
|
|
9
|
+
var psbt_js = require('@scure/btc-signer/psbt.js');
|
|
10
|
+
var secp256k1_js = require('@noble/curves/secp256k1.js');
|
|
11
|
+
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var bip68__namespace = /*#__PURE__*/_interopNamespace(bip68);
|
|
31
|
+
|
|
32
|
+
// src/utils/transaction.ts
|
|
33
|
+
var Transaction = class extends btcSigner.Transaction {
|
|
34
|
+
static ARK_TX_OPTS = {
|
|
35
|
+
allowUnknown: true,
|
|
36
|
+
allowUnknownOutputs: true,
|
|
37
|
+
allowUnknownInputs: true
|
|
38
|
+
};
|
|
39
|
+
constructor(opts) {
|
|
40
|
+
super(withArkOpts(opts));
|
|
41
|
+
}
|
|
42
|
+
static fromPSBT(psbt_, opts) {
|
|
43
|
+
return btcSigner.Transaction.fromPSBT(psbt_, withArkOpts(opts));
|
|
44
|
+
}
|
|
45
|
+
static fromRaw(raw, opts) {
|
|
46
|
+
return btcSigner.Transaction.fromRaw(raw, withArkOpts(opts));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function withArkOpts(opts) {
|
|
50
|
+
return { ...Transaction.ARK_TX_OPTS, ...opts };
|
|
51
|
+
}
|
|
52
|
+
function timelockToSequence(timelock) {
|
|
53
|
+
return bip68__namespace.encode(
|
|
54
|
+
timelock.type === "blocks" ? { blocks: Number(timelock.value) } : { seconds: Number(timelock.value) }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function sequenceToTimelock(sequence) {
|
|
58
|
+
const decoded = bip68__namespace.decode(sequence);
|
|
59
|
+
if ("blocks" in decoded && decoded.blocks !== void 0) {
|
|
60
|
+
return { type: "blocks", value: BigInt(decoded.blocks) };
|
|
61
|
+
}
|
|
62
|
+
if ("seconds" in decoded && decoded.seconds !== void 0) {
|
|
63
|
+
return { type: "seconds", value: BigInt(decoded.seconds) };
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Invalid BIP68 sequence: ${sequence}`);
|
|
66
|
+
}
|
|
67
|
+
var ArkPsbtFieldKey = /* @__PURE__ */ ((ArkPsbtFieldKey2) => {
|
|
68
|
+
ArkPsbtFieldKey2["VtxoTaprootTree"] = "taptree";
|
|
69
|
+
ArkPsbtFieldKey2["VtxoTreeExpiry"] = "expiry";
|
|
70
|
+
ArkPsbtFieldKey2["Cosigner"] = "cosigner";
|
|
71
|
+
ArkPsbtFieldKey2["ConditionWitness"] = "condition";
|
|
72
|
+
ArkPsbtFieldKey2["PrevArkTx"] = "prevarktx";
|
|
73
|
+
ArkPsbtFieldKey2["PrevoutTx"] = "prevouttx";
|
|
74
|
+
return ArkPsbtFieldKey2;
|
|
75
|
+
})(ArkPsbtFieldKey || {});
|
|
76
|
+
var ArkPsbtFieldKeyType = 222;
|
|
77
|
+
function setArkPsbtField(tx, inputIndex, coder, value) {
|
|
78
|
+
tx.updateInput(inputIndex, {
|
|
79
|
+
unknown: [...tx.getInput(inputIndex)?.unknown ?? [], coder.encode(value)]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function getArkPsbtFields(tx, inputIndex, coder) {
|
|
83
|
+
const unknown = tx.getInput(inputIndex)?.unknown ?? [];
|
|
84
|
+
const fields = [];
|
|
85
|
+
for (const u of unknown) {
|
|
86
|
+
const v = coder.decode(u);
|
|
87
|
+
if (v !== null) fields.push(v);
|
|
88
|
+
}
|
|
89
|
+
return fields;
|
|
90
|
+
}
|
|
91
|
+
var VtxoTaprootTree = {
|
|
92
|
+
key: "taptree" /* VtxoTaprootTree */,
|
|
93
|
+
encode: (value) => [
|
|
94
|
+
{
|
|
95
|
+
type: ArkPsbtFieldKeyType,
|
|
96
|
+
key: encodedPsbtFieldKey["taptree" /* VtxoTaprootTree */]
|
|
97
|
+
},
|
|
98
|
+
value
|
|
99
|
+
],
|
|
100
|
+
decode: (value) => nullIfCatch(() => {
|
|
101
|
+
if (!checkKeyMatch(value[0], "taptree" /* VtxoTaprootTree */)) return null;
|
|
102
|
+
return value[1];
|
|
103
|
+
})
|
|
104
|
+
};
|
|
105
|
+
var ConditionWitness = {
|
|
106
|
+
key: "condition" /* ConditionWitness */,
|
|
107
|
+
encode: (value) => [
|
|
108
|
+
{
|
|
109
|
+
type: ArkPsbtFieldKeyType,
|
|
110
|
+
key: encodedPsbtFieldKey["condition" /* ConditionWitness */]
|
|
111
|
+
},
|
|
112
|
+
btcSigner.RawWitness.encode(value)
|
|
113
|
+
],
|
|
114
|
+
decode: (value) => nullIfCatch(() => {
|
|
115
|
+
if (!checkKeyMatch(value[0], "condition" /* ConditionWitness */)) return null;
|
|
116
|
+
return btcSigner.RawWitness.decode(value[1]);
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
var PrevArkTxField = {
|
|
120
|
+
key: "prevarktx" /* PrevArkTx */,
|
|
121
|
+
encode: (value) => [
|
|
122
|
+
{
|
|
123
|
+
type: ArkPsbtFieldKeyType,
|
|
124
|
+
key: encodedPsbtFieldKey["prevarktx" /* PrevArkTx */]
|
|
125
|
+
},
|
|
126
|
+
value
|
|
127
|
+
],
|
|
128
|
+
decode: (value) => nullIfCatch(() => {
|
|
129
|
+
if (!checkKeyMatch(value[0], "prevarktx" /* PrevArkTx */)) return null;
|
|
130
|
+
return value[1];
|
|
131
|
+
})
|
|
132
|
+
};
|
|
133
|
+
var PrevoutTxField = {
|
|
134
|
+
key: "prevouttx" /* PrevoutTx */,
|
|
135
|
+
encode: (value) => [
|
|
136
|
+
{
|
|
137
|
+
type: ArkPsbtFieldKeyType,
|
|
138
|
+
key: encodedPsbtFieldKey["prevouttx" /* PrevoutTx */]
|
|
139
|
+
},
|
|
140
|
+
value
|
|
141
|
+
],
|
|
142
|
+
decode: (value) => nullIfCatch(() => {
|
|
143
|
+
if (!checkKeyMatch(value[0], "prevouttx" /* PrevoutTx */)) return null;
|
|
144
|
+
return value[1];
|
|
145
|
+
})
|
|
146
|
+
};
|
|
147
|
+
var CosignerPublicKey = {
|
|
148
|
+
key: "cosigner" /* Cosigner */,
|
|
149
|
+
encode: (value) => [
|
|
150
|
+
{
|
|
151
|
+
type: ArkPsbtFieldKeyType,
|
|
152
|
+
key: new Uint8Array([...encodedPsbtFieldKey["cosigner" /* Cosigner */], value.index])
|
|
153
|
+
},
|
|
154
|
+
value.key
|
|
155
|
+
],
|
|
156
|
+
decode: (unknown) => nullIfCatch(() => {
|
|
157
|
+
if (!checkKeyMatch(unknown[0], "cosigner" /* Cosigner */, true)) return null;
|
|
158
|
+
return {
|
|
159
|
+
index: unknown[0].key[unknown[0].key.length - 1],
|
|
160
|
+
key: unknown[1]
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
};
|
|
164
|
+
var VtxoTreeExpiry = {
|
|
165
|
+
key: "expiry" /* VtxoTreeExpiry */,
|
|
166
|
+
encode: (value) => [
|
|
167
|
+
{
|
|
168
|
+
type: ArkPsbtFieldKeyType,
|
|
169
|
+
key: encodedPsbtFieldKey["expiry" /* VtxoTreeExpiry */]
|
|
170
|
+
},
|
|
171
|
+
btcSigner.ScriptNum(6, true).encode(value.value === 0n ? 0n : value.value)
|
|
172
|
+
],
|
|
173
|
+
decode: (unknown) => nullIfCatch(() => {
|
|
174
|
+
if (!checkKeyMatch(unknown[0], "expiry" /* VtxoTreeExpiry */)) return null;
|
|
175
|
+
const v = btcSigner.ScriptNum(6, true).decode(unknown[1]);
|
|
176
|
+
if (!v) return null;
|
|
177
|
+
return sequenceToTimelock(Number(v));
|
|
178
|
+
})
|
|
179
|
+
};
|
|
180
|
+
var encodedPsbtFieldKey = Object.fromEntries(
|
|
181
|
+
Object.values(ArkPsbtFieldKey).map((key) => [key, new TextEncoder().encode(key)])
|
|
182
|
+
);
|
|
183
|
+
var nullIfCatch = (fn) => {
|
|
184
|
+
try {
|
|
185
|
+
return fn();
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function checkKeyMatch(key, arkPsbtFieldKey, prefixOnly = false) {
|
|
191
|
+
if (key.type !== ArkPsbtFieldKeyType) return false;
|
|
192
|
+
const expected = encodedPsbtFieldKey[arkPsbtFieldKey];
|
|
193
|
+
if (key.key.length < expected.length) return false;
|
|
194
|
+
if (!prefixOnly && key.key.length !== expected.length) return false;
|
|
195
|
+
for (let i = 0; i < expected.length; i++) {
|
|
196
|
+
if (key.key[i] !== expected[i]) return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
var getNetwork = (network) => {
|
|
201
|
+
const found = networks[network];
|
|
202
|
+
if (!found) throw new Error(`Unsupported network: ${network}`);
|
|
203
|
+
return found;
|
|
204
|
+
};
|
|
205
|
+
var networks = {
|
|
206
|
+
bitcoin: withArkPrefix(utils_js.NETWORK, "ark"),
|
|
207
|
+
testnet: withArkPrefix(utils_js.TEST_NETWORK, "tark"),
|
|
208
|
+
signet: withArkPrefix(utils_js.TEST_NETWORK, "tark"),
|
|
209
|
+
mutinynet: withArkPrefix(utils_js.TEST_NETWORK, "tark"),
|
|
210
|
+
regtest: withArkPrefix(
|
|
211
|
+
{
|
|
212
|
+
...utils_js.TEST_NETWORK,
|
|
213
|
+
bech32: "bcrt",
|
|
214
|
+
pubKeyHash: 111,
|
|
215
|
+
scriptHash: 196
|
|
216
|
+
},
|
|
217
|
+
"tark"
|
|
218
|
+
)
|
|
219
|
+
};
|
|
220
|
+
function withArkPrefix(network, prefix) {
|
|
221
|
+
return {
|
|
222
|
+
...network,
|
|
223
|
+
hrp: prefix
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
var DEFAULT_ARKADE_SERVER_URL = "https://arkade.computer";
|
|
227
|
+
var DEFAULT_NETWORK = networks.bitcoin;
|
|
228
|
+
var DEFAULT_NETWORK_NAME = "bitcoin";
|
|
229
|
+
var ArkAddress = class _ArkAddress {
|
|
230
|
+
/**
|
|
231
|
+
* Create an Arkade address from its server public key, Taproot output key, and prefix.
|
|
232
|
+
*
|
|
233
|
+
* @param serverPubKey - 32-byte Arkade server public key
|
|
234
|
+
* @param vtxoTaprootKey - 32-byte Taproot output key (a.k.a. tweaked public key)
|
|
235
|
+
* @param hrp - Bech32 human-readable prefix
|
|
236
|
+
* @param version - Address version byte
|
|
237
|
+
* @defaultValue `version = 0`
|
|
238
|
+
* @throws Error if either public key is not 32 bytes long
|
|
239
|
+
*/
|
|
240
|
+
constructor(serverPubKey, vtxoTaprootKey, hrp = DEFAULT_NETWORK.hrp, version2 = 0) {
|
|
241
|
+
this.serverPubKey = serverPubKey;
|
|
242
|
+
this.vtxoTaprootKey = vtxoTaprootKey;
|
|
243
|
+
this.hrp = hrp;
|
|
244
|
+
this.version = version2;
|
|
245
|
+
if (serverPubKey.length !== 32) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"Invalid server public key length, expected 32 bytes, got " + serverPubKey.length
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (vtxoTaprootKey.length !== 32) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"Invalid vtxo taproot public key length, expected 32 bytes, got " + vtxoTaprootKey.length
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
serverPubKey;
|
|
257
|
+
vtxoTaprootKey;
|
|
258
|
+
hrp;
|
|
259
|
+
version;
|
|
260
|
+
/**
|
|
261
|
+
* Decode an Arkade address from its bech32m string form.
|
|
262
|
+
*
|
|
263
|
+
* @param address - Bech32m-encoded Arkade address
|
|
264
|
+
* @returns Decoded Arkade address
|
|
265
|
+
* @throws Error if the address is malformed or has an invalid payload length
|
|
266
|
+
* @see encode
|
|
267
|
+
*/
|
|
268
|
+
static decode(address) {
|
|
269
|
+
const decoded = base.bech32m.decodeUnsafe(address, 1023);
|
|
270
|
+
if (!decoded) {
|
|
271
|
+
throw new Error("Invalid address");
|
|
272
|
+
}
|
|
273
|
+
const data = new Uint8Array(base.bech32m.fromWords(decoded.words));
|
|
274
|
+
if (data.length !== 1 + 32 + 32) {
|
|
275
|
+
throw new Error("Invalid data length, expected 65 bytes, got " + data.length);
|
|
276
|
+
}
|
|
277
|
+
const version2 = data[0];
|
|
278
|
+
const serverPubKey = data.slice(1, 33);
|
|
279
|
+
const vtxoTaprootPubKey = data.slice(33, 65);
|
|
280
|
+
return new _ArkAddress(serverPubKey, vtxoTaprootPubKey, decoded.prefix, version2);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Encode the address to its bech32m string form.
|
|
284
|
+
*
|
|
285
|
+
* @returns Bech32m-encoded Arkade address
|
|
286
|
+
* @see decode
|
|
287
|
+
*/
|
|
288
|
+
encode() {
|
|
289
|
+
const data = new Uint8Array(1 + 32 + 32);
|
|
290
|
+
data[0] = this.version;
|
|
291
|
+
data.set(this.serverPubKey, 1);
|
|
292
|
+
data.set(this.vtxoTaprootKey, 33);
|
|
293
|
+
const words = base.bech32m.toWords(data);
|
|
294
|
+
return base.bech32m.encode(this.hrp, words, 1023);
|
|
295
|
+
}
|
|
296
|
+
/** ScriptPubKey used to send non-dust funds to the address. */
|
|
297
|
+
get pkScript() {
|
|
298
|
+
return script_js.Script.encode(["OP_1", this.vtxoTaprootKey]);
|
|
299
|
+
}
|
|
300
|
+
/** ScriptPubKey used to send sub-dust funds to the address. */
|
|
301
|
+
get subdustPkScript() {
|
|
302
|
+
return script_js.Script.encode(["RETURN", this.vtxoTaprootKey]);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var MinimalScriptNum = btcSigner.ScriptNum(void 0, true);
|
|
306
|
+
function decodeTapscript(script) {
|
|
307
|
+
const types = [
|
|
308
|
+
exports.MultisigTapscript,
|
|
309
|
+
exports.CSVMultisigTapscript,
|
|
310
|
+
exports.ConditionCSVMultisigTapscript,
|
|
311
|
+
exports.ConditionMultisigTapscript,
|
|
312
|
+
exports.CLTVMultisigTapscript
|
|
313
|
+
];
|
|
314
|
+
for (const type of types) {
|
|
315
|
+
try {
|
|
316
|
+
return type.decode(script);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
throw new Error(`Failed to decode: script ${base.hex.encode(script)} is not a valid tapscript`);
|
|
322
|
+
}
|
|
323
|
+
exports.MultisigTapscript = void 0;
|
|
324
|
+
((MultisigTapscript2) => {
|
|
325
|
+
((MultisigType2) => {
|
|
326
|
+
MultisigType2[MultisigType2["CHECKSIG"] = 0] = "CHECKSIG";
|
|
327
|
+
MultisigType2[MultisigType2["CHECKSIGADD"] = 1] = "CHECKSIGADD";
|
|
328
|
+
})(MultisigTapscript2.MultisigType || (MultisigTapscript2.MultisigType = {}));
|
|
329
|
+
function encode2(params) {
|
|
330
|
+
if (params.pubkeys.length === 0) {
|
|
331
|
+
throw new Error("At least 1 pubkey is required");
|
|
332
|
+
}
|
|
333
|
+
for (const pubkey of params.pubkeys) {
|
|
334
|
+
if (pubkey.length !== 32) {
|
|
335
|
+
throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (!params.type) {
|
|
339
|
+
params.type = 0 /* CHECKSIG */;
|
|
340
|
+
}
|
|
341
|
+
if (params.type === 1 /* CHECKSIGADD */) {
|
|
342
|
+
return {
|
|
343
|
+
type: "multisig" /* Multisig */,
|
|
344
|
+
params,
|
|
345
|
+
script: btcSigner.p2tr_ms(params.pubkeys.length, params.pubkeys).script
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const asm = [];
|
|
349
|
+
for (let i = 0; i < params.pubkeys.length; i++) {
|
|
350
|
+
asm.push(params.pubkeys[i]);
|
|
351
|
+
if (i < params.pubkeys.length - 1) {
|
|
352
|
+
asm.push("CHECKSIGVERIFY");
|
|
353
|
+
} else {
|
|
354
|
+
asm.push("CHECKSIG");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
type: "multisig" /* Multisig */,
|
|
359
|
+
params,
|
|
360
|
+
script: btcSigner.Script.encode(asm)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
MultisigTapscript2.encode = encode2;
|
|
364
|
+
function decode2(script) {
|
|
365
|
+
if (script.length === 0) {
|
|
366
|
+
throw new Error("Failed to decode: script is empty");
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
return decodeChecksigAdd(script);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
try {
|
|
372
|
+
return decodeChecksig(script);
|
|
373
|
+
} catch (error2) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Failed to decode script: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
MultisigTapscript2.decode = decode2;
|
|
381
|
+
function decodeChecksigAdd(script) {
|
|
382
|
+
const asm = btcSigner.Script.decode(script);
|
|
383
|
+
const pubkeys = [];
|
|
384
|
+
let foundNumEqual = false;
|
|
385
|
+
for (let i = 0; i < asm.length; i++) {
|
|
386
|
+
const op = asm[i];
|
|
387
|
+
if (typeof op !== "string" && typeof op !== "number") {
|
|
388
|
+
if (op.length !== 32) {
|
|
389
|
+
throw new Error(`Invalid pubkey length: expected 32, got ${op.length}`);
|
|
390
|
+
}
|
|
391
|
+
pubkeys.push(op);
|
|
392
|
+
if (i + 1 >= asm.length || asm[i + 1] !== "CHECKSIGADD" && asm[i + 1] !== "CHECKSIG") {
|
|
393
|
+
throw new Error("Expected CHECKSIGADD or CHECKSIG after pubkey");
|
|
394
|
+
}
|
|
395
|
+
i++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (i === asm.length - 1) {
|
|
399
|
+
if (op !== "NUMEQUAL") {
|
|
400
|
+
throw new Error("Expected NUMEQUAL at end of script");
|
|
401
|
+
}
|
|
402
|
+
foundNumEqual = true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!foundNumEqual) {
|
|
406
|
+
throw new Error("Missing NUMEQUAL operation");
|
|
407
|
+
}
|
|
408
|
+
if (pubkeys.length === 0) {
|
|
409
|
+
throw new Error("Invalid script: must have at least 1 pubkey");
|
|
410
|
+
}
|
|
411
|
+
const reconstructed = encode2({
|
|
412
|
+
pubkeys,
|
|
413
|
+
type: 1 /* CHECKSIGADD */
|
|
414
|
+
});
|
|
415
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
416
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
type: "multisig" /* Multisig */,
|
|
420
|
+
params: { pubkeys, type: 1 /* CHECKSIGADD */ },
|
|
421
|
+
script
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function decodeChecksig(script) {
|
|
425
|
+
const asm = btcSigner.Script.decode(script);
|
|
426
|
+
const pubkeys = [];
|
|
427
|
+
for (let i = 0; i < asm.length; i++) {
|
|
428
|
+
const op = asm[i];
|
|
429
|
+
if (typeof op !== "string" && typeof op !== "number") {
|
|
430
|
+
if (op.length !== 32) {
|
|
431
|
+
throw new Error(`Invalid pubkey length: expected 32, got ${op.length}`);
|
|
432
|
+
}
|
|
433
|
+
pubkeys.push(op);
|
|
434
|
+
if (i + 1 >= asm.length) {
|
|
435
|
+
throw new Error("Unexpected end of script");
|
|
436
|
+
}
|
|
437
|
+
const nextOp = asm[i + 1];
|
|
438
|
+
if (nextOp !== "CHECKSIGVERIFY" && nextOp !== "CHECKSIG") {
|
|
439
|
+
throw new Error("Expected CHECKSIGVERIFY or CHECKSIG after pubkey");
|
|
440
|
+
}
|
|
441
|
+
if (i === asm.length - 2 && nextOp !== "CHECKSIG") {
|
|
442
|
+
throw new Error("Last operation must be CHECKSIG");
|
|
443
|
+
}
|
|
444
|
+
i++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (pubkeys.length === 0) {
|
|
449
|
+
throw new Error("Invalid script: must have at least 1 pubkey");
|
|
450
|
+
}
|
|
451
|
+
const reconstructed = encode2({ pubkeys, type: 0 /* CHECKSIG */ });
|
|
452
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
453
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
type: "multisig" /* Multisig */,
|
|
457
|
+
params: { pubkeys, type: 0 /* CHECKSIG */ },
|
|
458
|
+
script
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function is(tapscript) {
|
|
462
|
+
return tapscript.type === "multisig" /* Multisig */;
|
|
463
|
+
}
|
|
464
|
+
MultisigTapscript2.is = is;
|
|
465
|
+
})(exports.MultisigTapscript || (exports.MultisigTapscript = {}));
|
|
466
|
+
exports.CSVMultisigTapscript = void 0;
|
|
467
|
+
((CSVMultisigTapscript2) => {
|
|
468
|
+
function encode2(params) {
|
|
469
|
+
for (const pubkey of params.pubkeys) {
|
|
470
|
+
if (pubkey.length !== 32) {
|
|
471
|
+
throw new Error(`Invalid pubkey length: expected 32, got ${pubkey.length}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const sequence = MinimalScriptNum.encode(BigInt(timelockToSequence(params.timelock)));
|
|
475
|
+
const asm = [
|
|
476
|
+
sequence.length === 1 ? sequence[0] : sequence,
|
|
477
|
+
"CHECKSEQUENCEVERIFY",
|
|
478
|
+
"DROP"
|
|
479
|
+
];
|
|
480
|
+
const multisigScript = exports.MultisigTapscript.encode(params);
|
|
481
|
+
const script = new Uint8Array([...btcSigner.Script.encode(asm), ...multisigScript.script]);
|
|
482
|
+
return {
|
|
483
|
+
type: "csv-multisig" /* CSVMultisig */,
|
|
484
|
+
params,
|
|
485
|
+
script
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
CSVMultisigTapscript2.encode = encode2;
|
|
489
|
+
function decode2(script) {
|
|
490
|
+
if (script.length === 0) {
|
|
491
|
+
throw new Error("Failed to decode: script is empty");
|
|
492
|
+
}
|
|
493
|
+
const isValid = isScriptValid(script);
|
|
494
|
+
if (isValid instanceof Error) {
|
|
495
|
+
throw isValid;
|
|
496
|
+
}
|
|
497
|
+
const asm = btcSigner.Script.decode(script);
|
|
498
|
+
const sequence = asm[0];
|
|
499
|
+
const multisigScript = new Uint8Array(btcSigner.Script.encode(asm.slice(3)));
|
|
500
|
+
let multisig;
|
|
501
|
+
try {
|
|
502
|
+
multisig = exports.MultisigTapscript.decode(multisigScript);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
let sequenceNum;
|
|
509
|
+
if (typeof sequence === "number") {
|
|
510
|
+
sequenceNum = sequence;
|
|
511
|
+
} else {
|
|
512
|
+
sequenceNum = Number(MinimalScriptNum.decode(sequence));
|
|
513
|
+
}
|
|
514
|
+
const timelock = sequenceToTimelock(sequenceNum);
|
|
515
|
+
const reconstructed = encode2({
|
|
516
|
+
timelock,
|
|
517
|
+
...multisig.params
|
|
518
|
+
});
|
|
519
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
520
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
type: "csv-multisig" /* CSVMultisig */,
|
|
524
|
+
params: {
|
|
525
|
+
timelock,
|
|
526
|
+
...multisig.params
|
|
527
|
+
},
|
|
528
|
+
script
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
CSVMultisigTapscript2.decode = decode2;
|
|
532
|
+
function is(tapscript) {
|
|
533
|
+
return tapscript.type === "csv-multisig" /* CSVMultisig */;
|
|
534
|
+
}
|
|
535
|
+
CSVMultisigTapscript2.is = is;
|
|
536
|
+
function isScriptValid(script) {
|
|
537
|
+
const asm = btcSigner.Script.decode(script);
|
|
538
|
+
if (asm.length < 3) {
|
|
539
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
540
|
+
}
|
|
541
|
+
const sequence = asm[0];
|
|
542
|
+
if (typeof sequence === "string") {
|
|
543
|
+
return new Error("Invalid script: expected sequence number");
|
|
544
|
+
}
|
|
545
|
+
if (asm[1] !== "CHECKSEQUENCEVERIFY" || asm[2] !== "DROP") {
|
|
546
|
+
return new Error("Invalid script: expected CHECKSEQUENCEVERIFY DROP");
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
CSVMultisigTapscript2.isScriptValid = isScriptValid;
|
|
551
|
+
})(exports.CSVMultisigTapscript || (exports.CSVMultisigTapscript = {}));
|
|
552
|
+
exports.ConditionCSVMultisigTapscript = void 0;
|
|
553
|
+
((ConditionCSVMultisigTapscript2) => {
|
|
554
|
+
function encode2(params) {
|
|
555
|
+
const script = new Uint8Array([
|
|
556
|
+
...params.conditionScript,
|
|
557
|
+
...btcSigner.Script.encode(["VERIFY"]),
|
|
558
|
+
...exports.CSVMultisigTapscript.encode(params).script
|
|
559
|
+
]);
|
|
560
|
+
return {
|
|
561
|
+
type: "condition-csv-multisig" /* ConditionCSVMultisig */,
|
|
562
|
+
params,
|
|
563
|
+
script
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
ConditionCSVMultisigTapscript2.encode = encode2;
|
|
567
|
+
function decode2(script) {
|
|
568
|
+
if (script.length === 0) {
|
|
569
|
+
throw new Error("Failed to decode: script is empty");
|
|
570
|
+
}
|
|
571
|
+
const isValid = isScriptValid(script);
|
|
572
|
+
if (isValid instanceof Error) {
|
|
573
|
+
throw isValid;
|
|
574
|
+
}
|
|
575
|
+
const asm = btcSigner.Script.decode(script);
|
|
576
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
577
|
+
if (verifyIndex === -1) {
|
|
578
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
579
|
+
}
|
|
580
|
+
const conditionScript = new Uint8Array(btcSigner.Script.encode(asm.slice(0, verifyIndex)));
|
|
581
|
+
const csvMultisigScript = new Uint8Array(btcSigner.Script.encode(asm.slice(verifyIndex + 1)));
|
|
582
|
+
let csvMultisig;
|
|
583
|
+
try {
|
|
584
|
+
csvMultisig = exports.CSVMultisigTapscript.decode(csvMultisigScript);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`Invalid CSV multisig script: ${error instanceof Error ? error.message : String(error)}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const reconstructed = encode2({
|
|
591
|
+
conditionScript,
|
|
592
|
+
...csvMultisig.params
|
|
593
|
+
});
|
|
594
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
595
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
type: "condition-csv-multisig" /* ConditionCSVMultisig */,
|
|
599
|
+
params: {
|
|
600
|
+
conditionScript,
|
|
601
|
+
...csvMultisig.params
|
|
602
|
+
},
|
|
603
|
+
script
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
ConditionCSVMultisigTapscript2.decode = decode2;
|
|
607
|
+
function is(tapscript) {
|
|
608
|
+
return tapscript.type === "condition-csv-multisig" /* ConditionCSVMultisig */;
|
|
609
|
+
}
|
|
610
|
+
ConditionCSVMultisigTapscript2.is = is;
|
|
611
|
+
function getVerifyIndex(asm) {
|
|
612
|
+
let verifyIndex = -1;
|
|
613
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
614
|
+
if (asm[i] === "VERIFY") {
|
|
615
|
+
verifyIndex = i;
|
|
616
|
+
return verifyIndex;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return verifyIndex;
|
|
620
|
+
}
|
|
621
|
+
function isScriptValid(script) {
|
|
622
|
+
const asm = btcSigner.Script.decode(script);
|
|
623
|
+
if (asm.length < 1) {
|
|
624
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
625
|
+
}
|
|
626
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
627
|
+
if (verifyIndex === -1) {
|
|
628
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
629
|
+
}
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
ConditionCSVMultisigTapscript2.isScriptValid = isScriptValid;
|
|
633
|
+
})(exports.ConditionCSVMultisigTapscript || (exports.ConditionCSVMultisigTapscript = {}));
|
|
634
|
+
exports.ConditionMultisigTapscript = void 0;
|
|
635
|
+
((ConditionMultisigTapscript2) => {
|
|
636
|
+
function encode2(params) {
|
|
637
|
+
const script = new Uint8Array([
|
|
638
|
+
...params.conditionScript,
|
|
639
|
+
...btcSigner.Script.encode(["VERIFY"]),
|
|
640
|
+
...exports.MultisigTapscript.encode(params).script
|
|
641
|
+
]);
|
|
642
|
+
return {
|
|
643
|
+
type: "condition-multisig" /* ConditionMultisig */,
|
|
644
|
+
params,
|
|
645
|
+
script
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
ConditionMultisigTapscript2.encode = encode2;
|
|
649
|
+
function decode2(script) {
|
|
650
|
+
if (script.length === 0) {
|
|
651
|
+
throw new Error("Failed to decode: script is empty");
|
|
652
|
+
}
|
|
653
|
+
const isValid = isScriptValid(script);
|
|
654
|
+
if (isValid instanceof Error) {
|
|
655
|
+
throw isValid;
|
|
656
|
+
}
|
|
657
|
+
const asm = btcSigner.Script.decode(script);
|
|
658
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
659
|
+
if (verifyIndex === -1) {
|
|
660
|
+
throw Error("Invalid script: missing VERIFY operation");
|
|
661
|
+
}
|
|
662
|
+
const conditionScript = new Uint8Array(btcSigner.Script.encode(asm.slice(0, verifyIndex)));
|
|
663
|
+
const multisigScript = new Uint8Array(btcSigner.Script.encode(asm.slice(verifyIndex + 1)));
|
|
664
|
+
let multisig;
|
|
665
|
+
try {
|
|
666
|
+
multisig = exports.MultisigTapscript.decode(multisigScript);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const reconstructed = encode2({
|
|
673
|
+
conditionScript,
|
|
674
|
+
...multisig.params
|
|
675
|
+
});
|
|
676
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
677
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
type: "condition-multisig" /* ConditionMultisig */,
|
|
681
|
+
params: {
|
|
682
|
+
conditionScript,
|
|
683
|
+
...multisig.params
|
|
684
|
+
},
|
|
685
|
+
script
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
ConditionMultisigTapscript2.decode = decode2;
|
|
689
|
+
function is(tapscript) {
|
|
690
|
+
return tapscript.type === "condition-multisig" /* ConditionMultisig */;
|
|
691
|
+
}
|
|
692
|
+
ConditionMultisigTapscript2.is = is;
|
|
693
|
+
function getVerifyIndex(asm) {
|
|
694
|
+
let verifyIndex = -1;
|
|
695
|
+
for (let i = asm.length - 1; i >= 0; i--) {
|
|
696
|
+
if (asm[i] === "VERIFY") {
|
|
697
|
+
verifyIndex = i;
|
|
698
|
+
return verifyIndex;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return verifyIndex;
|
|
702
|
+
}
|
|
703
|
+
function isScriptValid(script) {
|
|
704
|
+
const asm = btcSigner.Script.decode(script);
|
|
705
|
+
if (asm.length < 1) {
|
|
706
|
+
return new Error(`Invalid script: too short (expected at least 1)`);
|
|
707
|
+
}
|
|
708
|
+
let verifyIndex = getVerifyIndex(asm);
|
|
709
|
+
if (verifyIndex === -1) {
|
|
710
|
+
return new Error("Invalid script: missing VERIFY operation");
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
ConditionMultisigTapscript2.isScriptValid = isScriptValid;
|
|
715
|
+
})(exports.ConditionMultisigTapscript || (exports.ConditionMultisigTapscript = {}));
|
|
716
|
+
exports.CLTVMultisigTapscript = void 0;
|
|
717
|
+
((CLTVMultisigTapscript2) => {
|
|
718
|
+
function encode2(params) {
|
|
719
|
+
const locktime = MinimalScriptNum.encode(params.absoluteTimelock);
|
|
720
|
+
const asm = [
|
|
721
|
+
locktime.length === 1 ? locktime[0] : locktime,
|
|
722
|
+
"CHECKLOCKTIMEVERIFY",
|
|
723
|
+
"DROP"
|
|
724
|
+
];
|
|
725
|
+
const timelockedScript = btcSigner.Script.encode(asm);
|
|
726
|
+
const script = new Uint8Array([
|
|
727
|
+
...timelockedScript,
|
|
728
|
+
...exports.MultisigTapscript.encode(params).script
|
|
729
|
+
]);
|
|
730
|
+
return {
|
|
731
|
+
type: "cltv-multisig" /* CLTVMultisig */,
|
|
732
|
+
params,
|
|
733
|
+
script
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
CLTVMultisigTapscript2.encode = encode2;
|
|
737
|
+
function decode2(script) {
|
|
738
|
+
if (script.length === 0) {
|
|
739
|
+
throw new Error("Failed to decode: script is empty");
|
|
740
|
+
}
|
|
741
|
+
const isValid = isScriptValid(script);
|
|
742
|
+
if (isValid instanceof Error) {
|
|
743
|
+
throw isValid;
|
|
744
|
+
}
|
|
745
|
+
const asm = btcSigner.Script.decode(script);
|
|
746
|
+
const locktime = asm[0];
|
|
747
|
+
if (typeof locktime === "string") {
|
|
748
|
+
throw new Error("Invalid script: expected locktime number");
|
|
749
|
+
}
|
|
750
|
+
if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
|
|
751
|
+
throw new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
|
|
752
|
+
}
|
|
753
|
+
const multisigScript = new Uint8Array(btcSigner.Script.encode(asm.slice(3)));
|
|
754
|
+
let multisig;
|
|
755
|
+
try {
|
|
756
|
+
multisig = exports.MultisigTapscript.decode(multisigScript);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
throw new Error(
|
|
759
|
+
`Invalid multisig script: ${error instanceof Error ? error.message : String(error)}`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
let absoluteTimelock;
|
|
763
|
+
if (typeof locktime === "number") {
|
|
764
|
+
absoluteTimelock = BigInt(locktime);
|
|
765
|
+
} else {
|
|
766
|
+
absoluteTimelock = MinimalScriptNum.decode(locktime);
|
|
767
|
+
}
|
|
768
|
+
const reconstructed = encode2({
|
|
769
|
+
absoluteTimelock,
|
|
770
|
+
...multisig.params
|
|
771
|
+
});
|
|
772
|
+
if (base.hex.encode(reconstructed.script) !== base.hex.encode(script)) {
|
|
773
|
+
throw new Error("Invalid script format: script reconstruction mismatch");
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
type: "cltv-multisig" /* CLTVMultisig */,
|
|
777
|
+
params: {
|
|
778
|
+
absoluteTimelock,
|
|
779
|
+
...multisig.params
|
|
780
|
+
},
|
|
781
|
+
script
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
CLTVMultisigTapscript2.decode = decode2;
|
|
785
|
+
function is(tapscript) {
|
|
786
|
+
return tapscript.type === "cltv-multisig" /* CLTVMultisig */;
|
|
787
|
+
}
|
|
788
|
+
CLTVMultisigTapscript2.is = is;
|
|
789
|
+
function isScriptValid(script) {
|
|
790
|
+
const asm = btcSigner.Script.decode(script);
|
|
791
|
+
if (asm.length < 3) {
|
|
792
|
+
return new Error(`Invalid script: too short (expected at least 3)`);
|
|
793
|
+
}
|
|
794
|
+
const locktime = asm[0];
|
|
795
|
+
if (typeof locktime === "string") {
|
|
796
|
+
return new Error("Invalid script: expected locktime as number or bytes");
|
|
797
|
+
}
|
|
798
|
+
if (asm[1] !== "CHECKLOCKTIMEVERIFY" || asm[2] !== "DROP") {
|
|
799
|
+
return new Error("Invalid script: expected CHECKLOCKTIMEVERIFY DROP");
|
|
800
|
+
}
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
CLTVMultisigTapscript2.isScriptValid = isScriptValid;
|
|
804
|
+
})(exports.CLTVMultisigTapscript || (exports.CLTVMultisigTapscript = {}));
|
|
805
|
+
function assembleBtcdTaprootTree(scripts) {
|
|
806
|
+
if (scripts.length === 0) {
|
|
807
|
+
throw new Error("assembleBtcdTaprootTree: empty scripts list");
|
|
808
|
+
}
|
|
809
|
+
const leaves = scripts.map((script) => ({
|
|
810
|
+
script,
|
|
811
|
+
leafVersion: payment_js.TAP_LEAF_VERSION
|
|
812
|
+
}));
|
|
813
|
+
if (leaves.length === 1) {
|
|
814
|
+
return leaves[0];
|
|
815
|
+
}
|
|
816
|
+
const branches = [];
|
|
817
|
+
for (let i = 0; i < leaves.length; i += 2) {
|
|
818
|
+
if (i === leaves.length - 1) {
|
|
819
|
+
const last = branches.pop();
|
|
820
|
+
if (last === void 0) {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`assembleBtcdTaprootTree: unexpected odd leaf at i=${i} with no prior branch`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
branches.push([last, leaves[i]]);
|
|
826
|
+
} else {
|
|
827
|
+
branches.push([leaves[i], leaves[i + 1]]);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
while (branches.length >= 2) {
|
|
831
|
+
const left = branches.shift();
|
|
832
|
+
const right = branches.shift();
|
|
833
|
+
branches.push([left, right]);
|
|
834
|
+
}
|
|
835
|
+
return branches[0];
|
|
836
|
+
}
|
|
837
|
+
var TapTreeCoder = psbt_js.PSBTOutput.tapTree[2];
|
|
838
|
+
function scriptFromTapLeafScript(leaf) {
|
|
839
|
+
return leaf[1].subarray(0, leaf[1].length - 1);
|
|
840
|
+
}
|
|
841
|
+
var VtxoScript = class _VtxoScript {
|
|
842
|
+
/**
|
|
843
|
+
* Create a virtual output script from its tapleaf scripts.
|
|
844
|
+
*
|
|
845
|
+
* The Taproot script tree is assembled using btcd's algorithm
|
|
846
|
+
* (`txscript.AssembleTaprootScriptTree`) so the derived taproot output
|
|
847
|
+
* key agrees with arkd for any leaf count. `@scure/btc-signer`'s
|
|
848
|
+
* default `taprootListToTree` is a Huffman builder that only agrees
|
|
849
|
+
* with arkd for power-of-2 leaf counts.
|
|
850
|
+
*
|
|
851
|
+
* @param scripts - Raw tapscript bytes for each leaf
|
|
852
|
+
* @throws Error if the provided leaves cannot produce a valid Taproot tree
|
|
853
|
+
*/
|
|
854
|
+
constructor(scripts) {
|
|
855
|
+
this.scripts = scripts;
|
|
856
|
+
const tapTree = assembleBtcdTaprootTree(scripts);
|
|
857
|
+
const payment = btcSigner.p2tr(btcSigner.TAPROOT_UNSPENDABLE_KEY, tapTree, void 0, true);
|
|
858
|
+
if (!payment.tapLeafScript || payment.tapLeafScript.length !== scripts.length) {
|
|
859
|
+
throw new Error("invalid scripts");
|
|
860
|
+
}
|
|
861
|
+
this.leaves = payment.tapLeafScript;
|
|
862
|
+
this.tweakedPublicKey = payment.tweakedPubkey;
|
|
863
|
+
this.pkScript = payment.script;
|
|
864
|
+
}
|
|
865
|
+
scripts;
|
|
866
|
+
leaves;
|
|
867
|
+
tweakedPublicKey;
|
|
868
|
+
pkScript;
|
|
869
|
+
/**
|
|
870
|
+
* Decode a virtual output script from an encoded TapTree.
|
|
871
|
+
*
|
|
872
|
+
* @param tapTree - Encoded TapTree bytes
|
|
873
|
+
* @returns Decoded virtual output script
|
|
874
|
+
* @throws Error if the TapTree cannot be decoded into a valid script set
|
|
875
|
+
* @see encode
|
|
876
|
+
*/
|
|
877
|
+
static decode(tapTree) {
|
|
878
|
+
const leaves = TapTreeCoder.decode(tapTree);
|
|
879
|
+
const scripts = leaves.map((leaf) => leaf.script);
|
|
880
|
+
return new _VtxoScript(scripts);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Encode the virtual output script to a TapTree byte representation.
|
|
884
|
+
*
|
|
885
|
+
* @returns Encoded TapTree bytes
|
|
886
|
+
* @see decode
|
|
887
|
+
*/
|
|
888
|
+
encode() {
|
|
889
|
+
const tapTree = TapTreeCoder.encode(
|
|
890
|
+
this.scripts.map((script) => ({
|
|
891
|
+
depth: 1,
|
|
892
|
+
version: payment_js.TAP_LEAF_VERSION,
|
|
893
|
+
script
|
|
894
|
+
}))
|
|
895
|
+
);
|
|
896
|
+
return tapTree;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Build the Arkade address corresponding to this virtual output script.
|
|
900
|
+
*
|
|
901
|
+
* @param prefix - Bech32 human-readable prefix
|
|
902
|
+
* @param serverPubKey - 32-byte Arkade server public key
|
|
903
|
+
* @returns Arkade address for this script
|
|
904
|
+
* @see ArkAddress
|
|
905
|
+
*/
|
|
906
|
+
address(prefix = DEFAULT_NETWORK.hrp, serverPubKey) {
|
|
907
|
+
return new ArkAddress(serverPubKey, this.tweakedPublicKey, prefix);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Build the Taproot onchain address corresponding to this virtual output script.
|
|
911
|
+
*
|
|
912
|
+
* @param network - Bitcoin network descriptor
|
|
913
|
+
* @returns Taproot onchain address
|
|
914
|
+
* @see address
|
|
915
|
+
*/
|
|
916
|
+
onchainAddress(network = DEFAULT_NETWORK) {
|
|
917
|
+
return btcSigner.Address(network).encode({
|
|
918
|
+
type: "tr",
|
|
919
|
+
pubkey: this.tweakedPublicKey
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Look up a tapleaf script by its hex-encoded tapscript body.
|
|
924
|
+
*
|
|
925
|
+
* @param scriptHex - Hex-encoded tapscript body without the leaf version byte
|
|
926
|
+
* @returns Matching tapleaf script
|
|
927
|
+
* @throws Error if no matching leaf exists
|
|
928
|
+
*/
|
|
929
|
+
findLeaf(scriptHex) {
|
|
930
|
+
const leaf = this.leaves.find(
|
|
931
|
+
(leaf2) => base.hex.encode(scriptFromTapLeafScript(leaf2)) === scriptHex
|
|
932
|
+
);
|
|
933
|
+
if (!leaf) {
|
|
934
|
+
throw new Error(`leaf '${scriptHex}' not found`);
|
|
935
|
+
}
|
|
936
|
+
return leaf;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Return all unilateral exit paths embedded in the virtual output script.
|
|
940
|
+
*
|
|
941
|
+
* @returns CSV-based exit paths found in the leaves
|
|
942
|
+
* @see getSequence
|
|
943
|
+
*/
|
|
944
|
+
exitPaths() {
|
|
945
|
+
const paths = [];
|
|
946
|
+
for (const leaf of this.leaves) {
|
|
947
|
+
try {
|
|
948
|
+
const script = scriptFromTapLeafScript(leaf);
|
|
949
|
+
if (exports.CSVMultisigTapscript.isScriptValid(script) === true) {
|
|
950
|
+
const tapScript = exports.CSVMultisigTapscript.decode(script);
|
|
951
|
+
paths.push(tapScript);
|
|
952
|
+
} else if (exports.ConditionCSVMultisigTapscript.isScriptValid(script) === true) {
|
|
953
|
+
const tapScript = exports.ConditionCSVMultisigTapscript.decode(script);
|
|
954
|
+
paths.push(tapScript);
|
|
955
|
+
}
|
|
956
|
+
} catch (e) {
|
|
957
|
+
console.debug("Failed to decode script", e);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return paths;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
function getSequence(tapLeafScript) {
|
|
964
|
+
let sequence = void 0;
|
|
965
|
+
try {
|
|
966
|
+
const scriptWithLeafVersion = tapLeafScript[1];
|
|
967
|
+
const script = scriptWithLeafVersion.subarray(0, scriptWithLeafVersion.length - 1);
|
|
968
|
+
try {
|
|
969
|
+
const params = exports.CSVMultisigTapscript.decode(script).params;
|
|
970
|
+
sequence = timelockToSequence(params.timelock);
|
|
971
|
+
} catch {
|
|
972
|
+
const params = exports.CLTVMultisigTapscript.decode(script).params;
|
|
973
|
+
sequence = Number(params.absoluteTimelock);
|
|
974
|
+
}
|
|
975
|
+
} catch {
|
|
976
|
+
}
|
|
977
|
+
return sequence;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// package.json
|
|
981
|
+
var version = "0.4.55";
|
|
982
|
+
|
|
983
|
+
// src/utils/fetch.ts
|
|
984
|
+
var buildVersion = "0.9.9";
|
|
985
|
+
var sdkVersion = `ts-sdk/${version}`;
|
|
986
|
+
var FetchError = class extends Error {
|
|
987
|
+
/** The request URL that failed, when derivable from the `fetch` input. */
|
|
988
|
+
url;
|
|
989
|
+
/** The HTTP method of the failed request (defaults to `"GET"`). */
|
|
990
|
+
method;
|
|
991
|
+
constructor(message, options) {
|
|
992
|
+
super(message, { cause: options.cause });
|
|
993
|
+
this.name = "FetchError";
|
|
994
|
+
this.url = options.url;
|
|
995
|
+
this.method = options.method;
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
function baseFetch(input, init) {
|
|
999
|
+
if (typeof globalThis.fetch !== "function") {
|
|
1000
|
+
throw new Error("Fetch API is not available in this environment.");
|
|
1001
|
+
}
|
|
1002
|
+
return globalThis.fetch(input, init).catch((cause) => {
|
|
1003
|
+
const { url, method } = describeRequest(input, init);
|
|
1004
|
+
throw new FetchError(`Network request failed: ${method} ${url}`, { url, method, cause });
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
function fetch(input, init) {
|
|
1008
|
+
const headers = new Headers(init?.headers);
|
|
1009
|
+
headers.set("X-Build-Version", buildVersion);
|
|
1010
|
+
headers.set("X-SDK-VERSION", sdkVersion);
|
|
1011
|
+
return baseFetch(input, { ...init, headers });
|
|
1012
|
+
}
|
|
1013
|
+
function describeRequest(input, init) {
|
|
1014
|
+
let url;
|
|
1015
|
+
if (typeof input === "string") {
|
|
1016
|
+
url = input;
|
|
1017
|
+
} else if (input instanceof URL) {
|
|
1018
|
+
url = input.href;
|
|
1019
|
+
} else {
|
|
1020
|
+
url = input.url;
|
|
1021
|
+
}
|
|
1022
|
+
let method;
|
|
1023
|
+
if (init?.method !== void 0) {
|
|
1024
|
+
method = init.method;
|
|
1025
|
+
} else if (input instanceof Request) {
|
|
1026
|
+
method = input.method;
|
|
1027
|
+
} else {
|
|
1028
|
+
method = "GET";
|
|
1029
|
+
}
|
|
1030
|
+
return { url, method };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/providers/errors.ts
|
|
1034
|
+
var ArkError = class extends Error {
|
|
1035
|
+
constructor(code, message, name, metadata) {
|
|
1036
|
+
super(message);
|
|
1037
|
+
this.code = code;
|
|
1038
|
+
this.message = message;
|
|
1039
|
+
this.name = name;
|
|
1040
|
+
this.metadata = metadata;
|
|
1041
|
+
}
|
|
1042
|
+
code;
|
|
1043
|
+
message;
|
|
1044
|
+
name;
|
|
1045
|
+
metadata;
|
|
1046
|
+
};
|
|
1047
|
+
var ArkErrorName = {
|
|
1048
|
+
DIGEST_MISMATCH: "DIGEST_MISMATCH",
|
|
1049
|
+
VTXO_ALREADY_SPENT: "VTXO_ALREADY_SPENT",
|
|
1050
|
+
INVALID_TX_FILTER: "INVALID_TX_FILTER",
|
|
1051
|
+
TX_FILTERS_LIMIT_EXCEEDED: "TX_FILTERS_LIMIT_EXCEEDED",
|
|
1052
|
+
/**
|
|
1053
|
+
* A CLTV closure was spent before its absolute locktime matured. Raised only by
|
|
1054
|
+
* `submitTx` (never `finalizeTx`), with metadata
|
|
1055
|
+
* `{ locktime, current_locktime, type: "height" | "time" }`.
|
|
1056
|
+
*
|
|
1057
|
+
* Self-healing, so defer and retry rather than fail: the server matures a
|
|
1058
|
+
* seconds-locktime against the **chain tip block's timestamp**, not its wall clock,
|
|
1059
|
+
* so a spend attempted promptly at maturity is rejected until a later block lands.
|
|
1060
|
+
*/
|
|
1061
|
+
FORFEIT_CLOSURE_LOCKED: "FORFEIT_CLOSURE_LOCKED"
|
|
1062
|
+
};
|
|
1063
|
+
function isArkError(error, name) {
|
|
1064
|
+
return error instanceof ArkError && (name === void 0 || error.name === name);
|
|
1065
|
+
}
|
|
1066
|
+
var ProviderUnavailableError = class extends Error {
|
|
1067
|
+
/** Always `true`: this error type only ever wraps retryable conditions. */
|
|
1068
|
+
retryable = true;
|
|
1069
|
+
constructor(message, options) {
|
|
1070
|
+
super(message, { cause: options?.cause });
|
|
1071
|
+
this.name = "ProviderUnavailableError";
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
var ServerResponseMismatchError = class extends Error {
|
|
1075
|
+
retryable = false;
|
|
1076
|
+
constructor(message, options) {
|
|
1077
|
+
super(message, { cause: options?.cause });
|
|
1078
|
+
this.name = "ServerResponseMismatchError";
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
function throwIfHttpUnavailable(response, kind, body) {
|
|
1082
|
+
if (body !== void 0 && maybeArkError(new Error(body))) return;
|
|
1083
|
+
if (response.status === 429 || response.status >= 500) {
|
|
1084
|
+
throw new ProviderUnavailableError(
|
|
1085
|
+
`${kind} unavailable: ${response.status} ${response.statusText}`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function toProviderUnavailable(err, kind) {
|
|
1090
|
+
if (err instanceof FetchError) {
|
|
1091
|
+
return new ProviderUnavailableError(`${kind} request failed`, { cause: err });
|
|
1092
|
+
}
|
|
1093
|
+
return err;
|
|
1094
|
+
}
|
|
1095
|
+
function maybeArkError(error) {
|
|
1096
|
+
try {
|
|
1097
|
+
if (!(error instanceof Error)) return void 0;
|
|
1098
|
+
const decoded = JSON.parse(error.message);
|
|
1099
|
+
if (Array.isArray(decoded.details)) {
|
|
1100
|
+
for (const details of decoded.details) {
|
|
1101
|
+
if (!("@type" in details)) continue;
|
|
1102
|
+
const type = details["@type"];
|
|
1103
|
+
if (type !== "type.googleapis.com/ark.v1.ErrorDetails") continue;
|
|
1104
|
+
if (!("code" in details)) continue;
|
|
1105
|
+
const code = details.code;
|
|
1106
|
+
if (!("message" in details)) continue;
|
|
1107
|
+
const message = details.message;
|
|
1108
|
+
if (!("name" in details)) continue;
|
|
1109
|
+
const name = details.name;
|
|
1110
|
+
let metadata;
|
|
1111
|
+
if ("metadata" in details && isMetadata(details.metadata)) {
|
|
1112
|
+
metadata = details.metadata;
|
|
1113
|
+
}
|
|
1114
|
+
return new ArkError(code, message, name, metadata);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof decoded.message === "string") {
|
|
1118
|
+
const m = decoded.message.match(/^([A-Z][A-Z0-9_]*) \((\d+)\): ([\s\S]*)$/);
|
|
1119
|
+
if (m) return new ArkError(Number(m[2]), m[3], m[1]);
|
|
1120
|
+
}
|
|
1121
|
+
return void 0;
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
return void 0;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function isMetadata(value) {
|
|
1127
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1128
|
+
}
|
|
1129
|
+
exports.Intent = void 0;
|
|
1130
|
+
((Intent2) => {
|
|
1131
|
+
function create(message, ins, outputs = []) {
|
|
1132
|
+
if (typeof message !== "string") {
|
|
1133
|
+
message = encodeMessage(message);
|
|
1134
|
+
}
|
|
1135
|
+
if (ins.length == 0) throw new Error("intent proof requires at least one input");
|
|
1136
|
+
const inputs = ins.map(prepareCoinAsIntentProofInput);
|
|
1137
|
+
if (!validateInputs(inputs)) ;
|
|
1138
|
+
if (!validateOutputs(outputs)) ;
|
|
1139
|
+
const toSpend = craftToSpendTx(message, inputs[0].witnessUtxo.script);
|
|
1140
|
+
return craftToSignTx(toSpend, inputs, outputs, message);
|
|
1141
|
+
}
|
|
1142
|
+
Intent2.create = create;
|
|
1143
|
+
function fee(proof) {
|
|
1144
|
+
let sumOfInputs = 0n;
|
|
1145
|
+
for (let i = 0; i < proof.inputsLength; i++) {
|
|
1146
|
+
const input = proof.getInput(i);
|
|
1147
|
+
if (input.witnessUtxo === void 0)
|
|
1148
|
+
throw new Error("intent proof input requires witness utxo");
|
|
1149
|
+
sumOfInputs += input.witnessUtxo.amount;
|
|
1150
|
+
}
|
|
1151
|
+
let sumOfOutputs = 0n;
|
|
1152
|
+
for (let i = 0; i < proof.outputsLength; i++) {
|
|
1153
|
+
const output = proof.getOutput(i);
|
|
1154
|
+
if (output.amount === void 0) throw new Error("intent proof output requires amount");
|
|
1155
|
+
sumOfOutputs += output.amount;
|
|
1156
|
+
}
|
|
1157
|
+
if (sumOfOutputs > sumOfInputs) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`intent proof output amount is greater than input amount: ${sumOfOutputs} > ${sumOfInputs}`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return Number(sumOfInputs - sumOfOutputs);
|
|
1163
|
+
}
|
|
1164
|
+
Intent2.fee = fee;
|
|
1165
|
+
function encodeMessage(message) {
|
|
1166
|
+
switch (message.type) {
|
|
1167
|
+
case "register":
|
|
1168
|
+
return JSON.stringify({
|
|
1169
|
+
type: "register",
|
|
1170
|
+
onchain_output_indexes: message.onchain_output_indexes,
|
|
1171
|
+
valid_at: message.valid_at,
|
|
1172
|
+
expire_at: message.expire_at,
|
|
1173
|
+
cosigners_public_keys: message.cosigners_public_keys
|
|
1174
|
+
});
|
|
1175
|
+
case "delete":
|
|
1176
|
+
return JSON.stringify({
|
|
1177
|
+
type: "delete",
|
|
1178
|
+
expire_at: message.expire_at
|
|
1179
|
+
});
|
|
1180
|
+
case "get-pending-tx":
|
|
1181
|
+
return JSON.stringify({
|
|
1182
|
+
type: "get-pending-tx",
|
|
1183
|
+
expire_at: message.expire_at
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
Intent2.encodeMessage = encodeMessage;
|
|
1188
|
+
})(exports.Intent || (exports.Intent = {}));
|
|
1189
|
+
var OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([btcSigner.OP.RETURN]);
|
|
1190
|
+
var ZERO_32 = new Uint8Array(32).fill(0);
|
|
1191
|
+
var MAX_INDEX = 4294967295;
|
|
1192
|
+
var TAG_INTENT_PROOF = "ark-intent-proof-message";
|
|
1193
|
+
var PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE = 9;
|
|
1194
|
+
function validateInput(input) {
|
|
1195
|
+
if (input.index === void 0) throw new Error("intent proof input requires index");
|
|
1196
|
+
if (input.txid === void 0) throw new Error("intent proof input requires txid");
|
|
1197
|
+
if (input.witnessUtxo === void 0)
|
|
1198
|
+
throw new Error("intent proof input requires witness utxo");
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
function validateInputs(inputs) {
|
|
1202
|
+
inputs.forEach(validateInput);
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
function validateOutput(output) {
|
|
1206
|
+
if (output.amount === void 0) throw new Error("intent proof output requires amount");
|
|
1207
|
+
if (output.script === void 0) throw new Error("intent proof output requires script");
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
function validateOutputs(outputs) {
|
|
1211
|
+
outputs.forEach(validateOutput);
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
function craftToSpendTx(message, pkScript, tag = TAG_INTENT_PROOF) {
|
|
1215
|
+
const messageHash = hashMessage(message, tag);
|
|
1216
|
+
const tx = new Transaction({
|
|
1217
|
+
version: 0
|
|
1218
|
+
});
|
|
1219
|
+
tx.addInput({
|
|
1220
|
+
txid: ZERO_32,
|
|
1221
|
+
// zero hash
|
|
1222
|
+
index: MAX_INDEX,
|
|
1223
|
+
sequence: 0
|
|
1224
|
+
});
|
|
1225
|
+
tx.addOutput({
|
|
1226
|
+
amount: 0n,
|
|
1227
|
+
script: pkScript
|
|
1228
|
+
});
|
|
1229
|
+
tx.updateInput(0, {
|
|
1230
|
+
finalScriptSig: btcSigner.Script.encode(["OP_0", messageHash])
|
|
1231
|
+
});
|
|
1232
|
+
return tx;
|
|
1233
|
+
}
|
|
1234
|
+
function craftToSignTx(toSpend, inputs, outputs, message) {
|
|
1235
|
+
const firstInput = inputs[0];
|
|
1236
|
+
const tx = new Transaction({
|
|
1237
|
+
version: 2,
|
|
1238
|
+
lockTime: 0
|
|
1239
|
+
});
|
|
1240
|
+
tx.addInput({
|
|
1241
|
+
...firstInput,
|
|
1242
|
+
txid: toSpend.id,
|
|
1243
|
+
index: 0,
|
|
1244
|
+
witnessUtxo: {
|
|
1245
|
+
script: firstInput.witnessUtxo.script,
|
|
1246
|
+
amount: 0n
|
|
1247
|
+
},
|
|
1248
|
+
sighashType: btcSigner.SigHash.ALL
|
|
1249
|
+
});
|
|
1250
|
+
for (const [i, input] of inputs.entries()) {
|
|
1251
|
+
tx.addInput({
|
|
1252
|
+
...input,
|
|
1253
|
+
sighashType: btcSigner.SigHash.ALL
|
|
1254
|
+
});
|
|
1255
|
+
if (input.unknown?.length) {
|
|
1256
|
+
tx.updateInput(i + 1, {
|
|
1257
|
+
unknown: input.unknown
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (outputs.length === 0) {
|
|
1262
|
+
outputs = [
|
|
1263
|
+
{
|
|
1264
|
+
amount: 0n,
|
|
1265
|
+
script: OP_RETURN_EMPTY_PKSCRIPT
|
|
1266
|
+
}
|
|
1267
|
+
];
|
|
1268
|
+
}
|
|
1269
|
+
for (const output of outputs) {
|
|
1270
|
+
tx.addOutput({
|
|
1271
|
+
amount: output.amount,
|
|
1272
|
+
script: output.script
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
const global = tx.global;
|
|
1276
|
+
global.unknown = [
|
|
1277
|
+
...global.unknown ?? [],
|
|
1278
|
+
[
|
|
1279
|
+
{ type: PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE, key: new Uint8Array() },
|
|
1280
|
+
new TextEncoder().encode(message)
|
|
1281
|
+
]
|
|
1282
|
+
];
|
|
1283
|
+
return tx;
|
|
1284
|
+
}
|
|
1285
|
+
function hashMessage(message, tag = TAG_INTENT_PROOF) {
|
|
1286
|
+
return secp256k1_js.schnorr.utils.taggedHash(tag, new TextEncoder().encode(message));
|
|
1287
|
+
}
|
|
1288
|
+
function prepareCoinAsIntentProofInput(coin) {
|
|
1289
|
+
if (!("tapTree" in coin)) {
|
|
1290
|
+
return coin;
|
|
1291
|
+
}
|
|
1292
|
+
const vtxoScript = VtxoScript.decode(coin.tapTree);
|
|
1293
|
+
const sequence = getSequence(coin.intentTapLeafScript);
|
|
1294
|
+
const unknown = [VtxoTaprootTree.encode(coin.tapTree)];
|
|
1295
|
+
if (coin.extraWitness) {
|
|
1296
|
+
unknown.push(ConditionWitness.encode(coin.extraWitness));
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
txid: base.hex.decode(coin.txid),
|
|
1300
|
+
index: coin.vout,
|
|
1301
|
+
witnessUtxo: {
|
|
1302
|
+
amount: BigInt(coin.value),
|
|
1303
|
+
script: vtxoScript.pkScript
|
|
1304
|
+
},
|
|
1305
|
+
sequence,
|
|
1306
|
+
tapLeafScript: [coin.intentTapLeafScript],
|
|
1307
|
+
unknown
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/providers/utils.ts
|
|
1312
|
+
function createAbortError() {
|
|
1313
|
+
const error = new Error("EventSource closed");
|
|
1314
|
+
error.name = "AbortError";
|
|
1315
|
+
return error;
|
|
1316
|
+
}
|
|
1317
|
+
function eventSourceIterator(eventSource) {
|
|
1318
|
+
const messageQueue = [];
|
|
1319
|
+
const errorQueue = [];
|
|
1320
|
+
let messageResolve = null;
|
|
1321
|
+
let errorResolve = null;
|
|
1322
|
+
let closed = false;
|
|
1323
|
+
let cleanedUp = false;
|
|
1324
|
+
const cleanup = () => {
|
|
1325
|
+
if (cleanedUp) return;
|
|
1326
|
+
cleanedUp = true;
|
|
1327
|
+
eventSource.removeEventListener("message", messageHandler);
|
|
1328
|
+
eventSource.removeEventListener("error", errorHandler);
|
|
1329
|
+
};
|
|
1330
|
+
const close = () => {
|
|
1331
|
+
if (closed) return;
|
|
1332
|
+
closed = true;
|
|
1333
|
+
messageQueue.length = 0;
|
|
1334
|
+
errorQueue.length = 0;
|
|
1335
|
+
eventSource.close();
|
|
1336
|
+
cleanup();
|
|
1337
|
+
if (errorResolve) {
|
|
1338
|
+
const reject = errorResolve;
|
|
1339
|
+
messageResolve = null;
|
|
1340
|
+
errorResolve = null;
|
|
1341
|
+
reject(createAbortError());
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
const messageHandler = (event) => {
|
|
1345
|
+
if (closed) return;
|
|
1346
|
+
if (messageResolve) {
|
|
1347
|
+
const resolve = messageResolve;
|
|
1348
|
+
messageResolve = null;
|
|
1349
|
+
errorResolve = null;
|
|
1350
|
+
resolve(event);
|
|
1351
|
+
} else {
|
|
1352
|
+
messageQueue.push(event);
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
const errorHandler = () => {
|
|
1356
|
+
if (closed) return;
|
|
1357
|
+
const error = new Error("EventSource error");
|
|
1358
|
+
error.name = "EventSourceError";
|
|
1359
|
+
if (errorResolve) {
|
|
1360
|
+
const reject = errorResolve;
|
|
1361
|
+
messageResolve = null;
|
|
1362
|
+
errorResolve = null;
|
|
1363
|
+
reject(error);
|
|
1364
|
+
} else {
|
|
1365
|
+
errorQueue.push(error);
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
eventSource.addEventListener("message", messageHandler);
|
|
1369
|
+
eventSource.addEventListener("error", errorHandler);
|
|
1370
|
+
const gen = (async function* () {
|
|
1371
|
+
try {
|
|
1372
|
+
while (!closed) {
|
|
1373
|
+
if (messageQueue.length > 0) {
|
|
1374
|
+
yield messageQueue.shift();
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (errorQueue.length > 0) {
|
|
1378
|
+
const error = errorQueue.shift();
|
|
1379
|
+
throw error;
|
|
1380
|
+
}
|
|
1381
|
+
const result = await new Promise((resolve, reject) => {
|
|
1382
|
+
messageResolve = resolve;
|
|
1383
|
+
errorResolve = reject;
|
|
1384
|
+
}).finally(() => {
|
|
1385
|
+
messageResolve = null;
|
|
1386
|
+
errorResolve = null;
|
|
1387
|
+
});
|
|
1388
|
+
if (!closed && result) {
|
|
1389
|
+
yield result;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
} finally {
|
|
1393
|
+
closed = true;
|
|
1394
|
+
cleanup();
|
|
1395
|
+
eventSource.close();
|
|
1396
|
+
}
|
|
1397
|
+
})();
|
|
1398
|
+
const origReturn = gen.return.bind(gen);
|
|
1399
|
+
const managed = gen;
|
|
1400
|
+
managed.close = close;
|
|
1401
|
+
managed.return = (value) => {
|
|
1402
|
+
close();
|
|
1403
|
+
return origReturn(value);
|
|
1404
|
+
};
|
|
1405
|
+
return managed;
|
|
1406
|
+
}
|
|
1407
|
+
function isEventSourceError(error) {
|
|
1408
|
+
return error instanceof Error && error.name === "EventSourceError";
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/providers/rateGate.ts
|
|
1412
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
1413
|
+
var DEFAULT_COOLDOWN_MS = 5e3;
|
|
1414
|
+
var MAX_COOLDOWN_MS = 6e4;
|
|
1415
|
+
var DEFAULT_JITTER_MS = 500;
|
|
1416
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1417
|
+
function parseRetryAfterMs(header) {
|
|
1418
|
+
if (header === null || header === void 0) return void 0;
|
|
1419
|
+
const trimmed = header.trim();
|
|
1420
|
+
if (trimmed === "") return void 0;
|
|
1421
|
+
const seconds = Number(trimmed);
|
|
1422
|
+
if (Number.isFinite(seconds)) return seconds < 0 ? void 0 : seconds * 1e3;
|
|
1423
|
+
const at = Date.parse(trimmed);
|
|
1424
|
+
if (!Number.isNaN(at)) return Math.max(0, at - Date.now());
|
|
1425
|
+
return void 0;
|
|
1426
|
+
}
|
|
1427
|
+
function requestOrigin(input) {
|
|
1428
|
+
let url;
|
|
1429
|
+
if (typeof input === "string") {
|
|
1430
|
+
url = input;
|
|
1431
|
+
} else if (input instanceof URL) {
|
|
1432
|
+
url = input.href;
|
|
1433
|
+
} else {
|
|
1434
|
+
url = input.url;
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
return new URL(url).origin;
|
|
1438
|
+
} catch {
|
|
1439
|
+
return url;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
var OriginRateGate = class {
|
|
1443
|
+
states = /* @__PURE__ */ new Map();
|
|
1444
|
+
maxConcurrent;
|
|
1445
|
+
defaultCooldownMs;
|
|
1446
|
+
maxCooldownMs;
|
|
1447
|
+
jitterMs;
|
|
1448
|
+
constructor(options) {
|
|
1449
|
+
this.maxConcurrent = options?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
1450
|
+
this.defaultCooldownMs = options?.defaultCooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
1451
|
+
this.maxCooldownMs = options?.maxCooldownMs ?? MAX_COOLDOWN_MS;
|
|
1452
|
+
this.jitterMs = options?.jitterMs ?? DEFAULT_JITTER_MS;
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Run `fn` under the origin's concurrency cap and behind any active
|
|
1456
|
+
* cooldown. Put only the request in `fn`: the slot is released as soon as
|
|
1457
|
+
* it settles, so body parsing doesn't hold one.
|
|
1458
|
+
*/
|
|
1459
|
+
async run(input, fn) {
|
|
1460
|
+
const state = this.stateFor(requestOrigin(input));
|
|
1461
|
+
await this.acquire(state);
|
|
1462
|
+
try {
|
|
1463
|
+
for (; ; ) {
|
|
1464
|
+
const remaining = state.blockedUntil - Date.now();
|
|
1465
|
+
if (remaining <= 0) break;
|
|
1466
|
+
await sleep(remaining + Math.random() * this.jitterMs);
|
|
1467
|
+
}
|
|
1468
|
+
return await fn();
|
|
1469
|
+
} finally {
|
|
1470
|
+
this.release(state);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* {@link run} for a request whose response the gate should inspect: a `429`
|
|
1475
|
+
* is recorded *before* the slot is released. Reporting after `run()` resolves
|
|
1476
|
+
* is too late — `release()` hands the slot to the next queued waiter, which
|
|
1477
|
+
* resumes, sees no cooldown, and sends into the limiter that just refused us.
|
|
1478
|
+
*
|
|
1479
|
+
* Prefer this over `run` for anything returning a `Response`.
|
|
1480
|
+
*/
|
|
1481
|
+
runHttp(input, fn) {
|
|
1482
|
+
return this.run(input, async () => {
|
|
1483
|
+
const response = await fn();
|
|
1484
|
+
if (response.status === 429) {
|
|
1485
|
+
this.reportRateLimited(input, response.headers?.get("retry-after"));
|
|
1486
|
+
}
|
|
1487
|
+
return response;
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Record an observed `429`, pausing every gated request to that origin.
|
|
1492
|
+
* Monotonic: a shorter cooldown never shortens one already in effect.
|
|
1493
|
+
*/
|
|
1494
|
+
reportRateLimited(input, retryAfterHeader) {
|
|
1495
|
+
const cooldown = Math.min(
|
|
1496
|
+
parseRetryAfterMs(retryAfterHeader) ?? this.defaultCooldownMs,
|
|
1497
|
+
this.maxCooldownMs
|
|
1498
|
+
);
|
|
1499
|
+
const state = this.stateFor(requestOrigin(input));
|
|
1500
|
+
state.blockedUntil = Math.max(state.blockedUntil, Date.now() + cooldown);
|
|
1501
|
+
}
|
|
1502
|
+
/** Milliseconds left on `input` origin's cooldown; `0` when not cooling down. */
|
|
1503
|
+
cooldownRemainingMs(input) {
|
|
1504
|
+
const state = this.states.get(requestOrigin(input));
|
|
1505
|
+
if (!state) return 0;
|
|
1506
|
+
return Math.max(0, state.blockedUntil - Date.now());
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Drop all per-origin state, so test suites don't inherit each other's
|
|
1510
|
+
* cooldowns. Waiters queued on the discarded state still drain normally.
|
|
1511
|
+
*/
|
|
1512
|
+
reset() {
|
|
1513
|
+
this.states.clear();
|
|
1514
|
+
}
|
|
1515
|
+
stateFor(origin) {
|
|
1516
|
+
let state = this.states.get(origin);
|
|
1517
|
+
if (!state) {
|
|
1518
|
+
state = { blockedUntil: 0, active: 0, queue: [] };
|
|
1519
|
+
this.states.set(origin, state);
|
|
1520
|
+
}
|
|
1521
|
+
return state;
|
|
1522
|
+
}
|
|
1523
|
+
acquire(state) {
|
|
1524
|
+
if (state.active < this.maxConcurrent) {
|
|
1525
|
+
state.active += 1;
|
|
1526
|
+
return Promise.resolve();
|
|
1527
|
+
}
|
|
1528
|
+
return new Promise((resolve) => {
|
|
1529
|
+
state.queue.push(() => {
|
|
1530
|
+
state.active += 1;
|
|
1531
|
+
resolve();
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
release(state) {
|
|
1536
|
+
state.active -= 1;
|
|
1537
|
+
const next = state.queue.shift();
|
|
1538
|
+
if (next) next();
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
var rateGate = new OriginRateGate();
|
|
1542
|
+
|
|
1543
|
+
// src/providers/ark.ts
|
|
1544
|
+
var DigestMismatchError = class extends Error {
|
|
1545
|
+
constructor(message) {
|
|
1546
|
+
super(message);
|
|
1547
|
+
this.name = "DigestMismatchError";
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
var SettlementEventType = /* @__PURE__ */ ((SettlementEventType2) => {
|
|
1551
|
+
SettlementEventType2["BatchStarted"] = "batch_started";
|
|
1552
|
+
SettlementEventType2["BatchFinalization"] = "batch_finalization";
|
|
1553
|
+
SettlementEventType2["BatchFinalized"] = "batch_finalized";
|
|
1554
|
+
SettlementEventType2["BatchFailed"] = "batch_failed";
|
|
1555
|
+
SettlementEventType2["TreeSigningStarted"] = "tree_signing_started";
|
|
1556
|
+
SettlementEventType2["TreeNonces"] = "tree_nonces";
|
|
1557
|
+
SettlementEventType2["TreeTx"] = "tree_tx";
|
|
1558
|
+
SettlementEventType2["TreeSignature"] = "tree_signature";
|
|
1559
|
+
SettlementEventType2["StreamStarted"] = "stream_started";
|
|
1560
|
+
return SettlementEventType2;
|
|
1561
|
+
})(SettlementEventType || {});
|
|
1562
|
+
var RestArkProvider = class {
|
|
1563
|
+
constructor(serverUrl = DEFAULT_ARKADE_SERVER_URL) {
|
|
1564
|
+
this.serverUrl = serverUrl;
|
|
1565
|
+
}
|
|
1566
|
+
serverUrl;
|
|
1567
|
+
/**
|
|
1568
|
+
* Last server-info digest seen (from {@link getInfo}). Sent as `X-Digest`
|
|
1569
|
+
* on outgoing requests so arkd can reject a client whose cached info is
|
|
1570
|
+
* stale. Empty until the first {@link getInfo}.
|
|
1571
|
+
*/
|
|
1572
|
+
_digest = "";
|
|
1573
|
+
_serverInfoListeners = /* @__PURE__ */ new Set();
|
|
1574
|
+
/**
|
|
1575
|
+
* Subscribe to server-info changes. Fired when a request is rejected with
|
|
1576
|
+
* `DIGEST_MISMATCH` and fresh info is re-fetched, so consumers (the wallet)
|
|
1577
|
+
* can re-derive signer-dependent state mid-session without polling. Returns
|
|
1578
|
+
* an unsubscribe function.
|
|
1579
|
+
*/
|
|
1580
|
+
onServerInfoChanged(listener) {
|
|
1581
|
+
this._serverInfoListeners.add(listener);
|
|
1582
|
+
return () => {
|
|
1583
|
+
this._serverInfoListeners.delete(listener);
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
emitServerInfoChanged(info) {
|
|
1587
|
+
for (const listener of this._serverInfoListeners) {
|
|
1588
|
+
try {
|
|
1589
|
+
listener(info);
|
|
1590
|
+
} catch (e) {
|
|
1591
|
+
console.warn("onServerInfoChanged listener threw", e);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* `fetch` wrapper for arkd requests that participates in server-info digest
|
|
1597
|
+
* negotiation. Sends the cached `X-Digest`; when arkd rejects a request with
|
|
1598
|
+
* `DIGEST_MISMATCH`, refreshes {@link getInfo} (updating the digest), fires
|
|
1599
|
+
* {@link onServerInfoChanged}, and THROWS {@link DigestMismatchError} — it
|
|
1600
|
+
* never silently retries, since the in-flight request was built against the
|
|
1601
|
+
* now-stale config. Dormant until arkd returns the error — then it is the
|
|
1602
|
+
* instant, event-driven signer-rotation trigger. {@link getInfo} itself never
|
|
1603
|
+
* routes through here: it is the refresh path and must not be digest-gated.
|
|
1604
|
+
*/
|
|
1605
|
+
async authedFetch(url, init) {
|
|
1606
|
+
const digest = this._digest;
|
|
1607
|
+
const headers = {
|
|
1608
|
+
...init.headers
|
|
1609
|
+
};
|
|
1610
|
+
if (digest) headers["X-Digest"] = digest;
|
|
1611
|
+
let response;
|
|
1612
|
+
try {
|
|
1613
|
+
response = await fetch(url, { ...init, headers });
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
throw toProviderUnavailable(err, "arkade");
|
|
1616
|
+
}
|
|
1617
|
+
if (response.ok) return response;
|
|
1618
|
+
if (response.status === 429) {
|
|
1619
|
+
rateGate.reportRateLimited(url, response.headers?.get("retry-after"));
|
|
1620
|
+
}
|
|
1621
|
+
let body;
|
|
1622
|
+
try {
|
|
1623
|
+
body = await response.clone().text();
|
|
1624
|
+
} catch (e) {
|
|
1625
|
+
console.warn("authedFetch could not read response body for digest check", e);
|
|
1626
|
+
throwIfHttpUnavailable(response, "arkade");
|
|
1627
|
+
return response;
|
|
1628
|
+
}
|
|
1629
|
+
const arkError = maybeArkError(new Error(body));
|
|
1630
|
+
if (!arkError) throwIfHttpUnavailable(response, "arkade");
|
|
1631
|
+
if (!isArkError(arkError, ArkErrorName.DIGEST_MISMATCH)) return response;
|
|
1632
|
+
this._digest = "";
|
|
1633
|
+
const info = await this.getInfo();
|
|
1634
|
+
this.emitServerInfoChanged(info);
|
|
1635
|
+
throw new DigestMismatchError(
|
|
1636
|
+
"Arkade server reported a configuration digest mismatch; server info was refreshed. Rebuild and retry the request under the new server info."
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
async getInfo() {
|
|
1640
|
+
const url = `${this.serverUrl}/v1/info`;
|
|
1641
|
+
const response = await rateGate.runHttp(url, () => fetch(url));
|
|
1642
|
+
if (!response.ok) {
|
|
1643
|
+
const errorText = await response.text();
|
|
1644
|
+
throwIfHttpUnavailable(response, "arkade", errorText);
|
|
1645
|
+
handleError(errorText, `Failed to get server info: ${response.statusText}`);
|
|
1646
|
+
}
|
|
1647
|
+
const fromServer = await response.json();
|
|
1648
|
+
const info = {
|
|
1649
|
+
boardingExitDelay: BigInt(fromServer.boardingExitDelay ?? 0),
|
|
1650
|
+
checkpointTapscript: fromServer.checkpointTapscript ?? "",
|
|
1651
|
+
deprecatedSigners: fromServer.deprecatedSigners?.map((signer) => ({
|
|
1652
|
+
// arkd advertises `cutoffDate` as a non-nullable field, so it
|
|
1653
|
+
// is always a bigint here — `0n` is the sentinel for "no
|
|
1654
|
+
// cutoff" (the classifier maps it to DUE_NOW). The grpc-gateway
|
|
1655
|
+
// marshals with EmitUnpopulated, so an unset `cutoff_date`
|
|
1656
|
+
// already arrives as `"0"`; a genuinely missing field defaults
|
|
1657
|
+
// to `0n` too. Never collapse to `undefined`.
|
|
1658
|
+
cutoffDate: BigInt(signer.cutoffDate ?? 0),
|
|
1659
|
+
pubkey: signer.pubkey ?? ""
|
|
1660
|
+
})) ?? [],
|
|
1661
|
+
digest: fromServer.digest ?? "",
|
|
1662
|
+
dust: BigInt(fromServer.dust ?? 0),
|
|
1663
|
+
fees: {
|
|
1664
|
+
intentFee: fromServer.fees?.intentFee ?? {},
|
|
1665
|
+
txFeeRate: fromServer?.fees?.txFeeRate ?? ""
|
|
1666
|
+
},
|
|
1667
|
+
forfeitAddress: fromServer.forfeitAddress ?? "",
|
|
1668
|
+
forfeitPubkey: fromServer.forfeitPubkey ?? "",
|
|
1669
|
+
network: fromServer.network ?? "",
|
|
1670
|
+
scheduledSession: "scheduledSession" in fromServer && fromServer.scheduledSession != null ? {
|
|
1671
|
+
duration: BigInt(fromServer.scheduledSession.duration ?? 0),
|
|
1672
|
+
nextStartTime: BigInt(fromServer.scheduledSession.nextStartTime ?? 0),
|
|
1673
|
+
nextEndTime: BigInt(fromServer.scheduledSession.nextEndTime ?? 0),
|
|
1674
|
+
period: BigInt(fromServer.scheduledSession.period ?? 0),
|
|
1675
|
+
fees: fromServer.scheduledSession.fees ?? {}
|
|
1676
|
+
} : void 0,
|
|
1677
|
+
serviceStatus: fromServer.serviceStatus ?? {},
|
|
1678
|
+
sessionDuration: BigInt(fromServer.sessionDuration ?? 0),
|
|
1679
|
+
signerPubkey: fromServer.signerPubkey ?? "",
|
|
1680
|
+
unilateralExitDelay: BigInt(fromServer.unilateralExitDelay ?? 0),
|
|
1681
|
+
utxoMaxAmount: BigInt(fromServer.utxoMaxAmount ?? -1),
|
|
1682
|
+
utxoMinAmount: BigInt(fromServer.utxoMinAmount ?? 0),
|
|
1683
|
+
version: fromServer.version ?? "",
|
|
1684
|
+
vtxoMaxAmount: BigInt(fromServer.vtxoMaxAmount ?? -1),
|
|
1685
|
+
vtxoMinAmount: BigInt(fromServer.vtxoMinAmount ?? 0)
|
|
1686
|
+
};
|
|
1687
|
+
this._digest = info.digest;
|
|
1688
|
+
return info;
|
|
1689
|
+
}
|
|
1690
|
+
async submitTx(signedArkTx, checkpointTxs) {
|
|
1691
|
+
const url = `${this.serverUrl}/v1/tx/submit`;
|
|
1692
|
+
const response = await this.authedFetch(url, {
|
|
1693
|
+
method: "POST",
|
|
1694
|
+
headers: {
|
|
1695
|
+
"Content-Type": "application/json"
|
|
1696
|
+
},
|
|
1697
|
+
body: JSON.stringify({
|
|
1698
|
+
signedArkTx,
|
|
1699
|
+
checkpointTxs
|
|
1700
|
+
})
|
|
1701
|
+
});
|
|
1702
|
+
if (!response.ok) {
|
|
1703
|
+
const errorText = await response.text();
|
|
1704
|
+
handleError(errorText, `Failed to submit virtual transaction: ${errorText}`);
|
|
1705
|
+
}
|
|
1706
|
+
const data = await response.json();
|
|
1707
|
+
return {
|
|
1708
|
+
arkTxid: data.arkTxid,
|
|
1709
|
+
finalArkTx: data.finalArkTx,
|
|
1710
|
+
signedCheckpointTxs: data.signedCheckpointTxs
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
async finalizeTx(arkTxid, finalCheckpointTxs) {
|
|
1714
|
+
const url = `${this.serverUrl}/v1/tx/finalize`;
|
|
1715
|
+
const response = await this.authedFetch(url, {
|
|
1716
|
+
method: "POST",
|
|
1717
|
+
headers: {
|
|
1718
|
+
"Content-Type": "application/json"
|
|
1719
|
+
},
|
|
1720
|
+
body: JSON.stringify({
|
|
1721
|
+
arkTxid,
|
|
1722
|
+
finalCheckpointTxs
|
|
1723
|
+
})
|
|
1724
|
+
});
|
|
1725
|
+
if (!response.ok) {
|
|
1726
|
+
const errorText = await response.text();
|
|
1727
|
+
handleError(errorText, `Failed to finalize offchain transaction: ${errorText}`);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
async registerIntent(intent) {
|
|
1731
|
+
const url = `${this.serverUrl}/v1/batch/registerIntent`;
|
|
1732
|
+
const response = await this.authedFetch(url, {
|
|
1733
|
+
method: "POST",
|
|
1734
|
+
headers: {
|
|
1735
|
+
"Content-Type": "application/json"
|
|
1736
|
+
},
|
|
1737
|
+
body: JSON.stringify({
|
|
1738
|
+
intent: {
|
|
1739
|
+
proof: intent.proof,
|
|
1740
|
+
message: exports.Intent.encodeMessage(intent.message)
|
|
1741
|
+
}
|
|
1742
|
+
})
|
|
1743
|
+
});
|
|
1744
|
+
if (!response.ok) {
|
|
1745
|
+
const errorText = await response.text();
|
|
1746
|
+
handleError(errorText, `Failed to register intent: ${errorText}`);
|
|
1747
|
+
}
|
|
1748
|
+
const data = await response.json();
|
|
1749
|
+
return data.intentId;
|
|
1750
|
+
}
|
|
1751
|
+
async deleteIntent(intent) {
|
|
1752
|
+
const url = `${this.serverUrl}/v1/batch/deleteIntent`;
|
|
1753
|
+
const response = await this.authedFetch(url, {
|
|
1754
|
+
method: "POST",
|
|
1755
|
+
headers: {
|
|
1756
|
+
"Content-Type": "application/json"
|
|
1757
|
+
},
|
|
1758
|
+
body: JSON.stringify({
|
|
1759
|
+
intent: {
|
|
1760
|
+
proof: intent.proof,
|
|
1761
|
+
message: exports.Intent.encodeMessage(intent.message)
|
|
1762
|
+
}
|
|
1763
|
+
})
|
|
1764
|
+
});
|
|
1765
|
+
if (!response.ok) {
|
|
1766
|
+
const errorText = await response.text();
|
|
1767
|
+
handleError(errorText, `Failed to delete intent: ${errorText}`);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async confirmRegistration(intentId) {
|
|
1771
|
+
const url = `${this.serverUrl}/v1/batch/ack`;
|
|
1772
|
+
const response = await this.authedFetch(url, {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
headers: {
|
|
1775
|
+
"Content-Type": "application/json"
|
|
1776
|
+
},
|
|
1777
|
+
body: JSON.stringify({
|
|
1778
|
+
intentId
|
|
1779
|
+
})
|
|
1780
|
+
});
|
|
1781
|
+
if (!response.ok) {
|
|
1782
|
+
const errorText = await response.text();
|
|
1783
|
+
handleError(errorText, `Failed to confirm registration: ${errorText}`);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
async submitTreeNonces(batchId, pubkey, nonces) {
|
|
1787
|
+
const url = `${this.serverUrl}/v1/batch/tree/submitNonces`;
|
|
1788
|
+
const response = await this.authedFetch(url, {
|
|
1789
|
+
method: "POST",
|
|
1790
|
+
headers: {
|
|
1791
|
+
"Content-Type": "application/json"
|
|
1792
|
+
},
|
|
1793
|
+
body: JSON.stringify({
|
|
1794
|
+
batchId,
|
|
1795
|
+
pubkey,
|
|
1796
|
+
treeNonces: encodeMusig2Nonces(nonces)
|
|
1797
|
+
})
|
|
1798
|
+
});
|
|
1799
|
+
if (!response.ok) {
|
|
1800
|
+
const errorText = await response.text();
|
|
1801
|
+
handleError(errorText, `Failed to submit tree nonces: ${errorText}`);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
async submitTreeSignatures(batchId, pubkey, signatures) {
|
|
1805
|
+
const url = `${this.serverUrl}/v1/batch/tree/submitSignatures`;
|
|
1806
|
+
const response = await this.authedFetch(url, {
|
|
1807
|
+
method: "POST",
|
|
1808
|
+
headers: {
|
|
1809
|
+
"Content-Type": "application/json"
|
|
1810
|
+
},
|
|
1811
|
+
body: JSON.stringify({
|
|
1812
|
+
batchId,
|
|
1813
|
+
pubkey,
|
|
1814
|
+
treeSignatures: encodeMusig2Signatures(signatures)
|
|
1815
|
+
})
|
|
1816
|
+
});
|
|
1817
|
+
if (!response.ok) {
|
|
1818
|
+
const errorText = await response.text();
|
|
1819
|
+
handleError(errorText, `Failed to submit tree signatures: ${errorText}`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
async submitSignedForfeitTxs(signedForfeitTxs, signedCommitmentTx) {
|
|
1823
|
+
const url = `${this.serverUrl}/v1/batch/submitForfeitTxs`;
|
|
1824
|
+
const response = await this.authedFetch(url, {
|
|
1825
|
+
method: "POST",
|
|
1826
|
+
headers: {
|
|
1827
|
+
"Content-Type": "application/json"
|
|
1828
|
+
},
|
|
1829
|
+
body: JSON.stringify({
|
|
1830
|
+
signedForfeitTxs,
|
|
1831
|
+
signedCommitmentTx
|
|
1832
|
+
})
|
|
1833
|
+
});
|
|
1834
|
+
if (!response.ok) {
|
|
1835
|
+
const errorText = await response.text();
|
|
1836
|
+
handleError(errorText, `Failed to submit forfeit transactions: ${response.statusText}`);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
getEventStream(signal, topics) {
|
|
1840
|
+
const url = `${this.serverUrl}/v1/batch/events`;
|
|
1841
|
+
const queryParams = topics.length > 0 ? `?${topics.map((topic) => `topics=${encodeURIComponent(topic)}`).join("&")}` : "";
|
|
1842
|
+
let iterator = null;
|
|
1843
|
+
const closeIterator = () => iterator?.close();
|
|
1844
|
+
const self = this;
|
|
1845
|
+
const gen = (async function* () {
|
|
1846
|
+
const abortHandler = closeIterator;
|
|
1847
|
+
signal?.addEventListener("abort", abortHandler);
|
|
1848
|
+
try {
|
|
1849
|
+
while (!signal?.aborted) {
|
|
1850
|
+
const currentIterator = eventSourceIterator(new EventSource(url + queryParams));
|
|
1851
|
+
iterator = currentIterator;
|
|
1852
|
+
try {
|
|
1853
|
+
for await (const event of currentIterator) {
|
|
1854
|
+
if (signal?.aborted) break;
|
|
1855
|
+
try {
|
|
1856
|
+
const data = JSON.parse(event.data);
|
|
1857
|
+
const settlementEvent = self.parseSettlementEvent(data);
|
|
1858
|
+
if (settlementEvent) {
|
|
1859
|
+
yield settlementEvent;
|
|
1860
|
+
}
|
|
1861
|
+
} catch (err) {
|
|
1862
|
+
console.error("Failed to parse event:", err);
|
|
1863
|
+
throw err;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
} catch (error) {
|
|
1867
|
+
if (signal?.aborted || error instanceof Error && error.name === "AbortError") {
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
if (isFetchTimeoutError(error)) {
|
|
1871
|
+
console.debug("Timeout error ignored");
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
if (isEventSourceError(error)) {
|
|
1875
|
+
throw error;
|
|
1876
|
+
}
|
|
1877
|
+
console.error("Event stream error:", error);
|
|
1878
|
+
throw error;
|
|
1879
|
+
} finally {
|
|
1880
|
+
currentIterator.close();
|
|
1881
|
+
iterator = null;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
} finally {
|
|
1885
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
1886
|
+
closeIterator();
|
|
1887
|
+
}
|
|
1888
|
+
})();
|
|
1889
|
+
const origReturn = gen.return.bind(gen);
|
|
1890
|
+
gen.return = (value) => {
|
|
1891
|
+
closeIterator();
|
|
1892
|
+
return origReturn(value);
|
|
1893
|
+
};
|
|
1894
|
+
return gen;
|
|
1895
|
+
}
|
|
1896
|
+
getTransactionsStream(signal) {
|
|
1897
|
+
const url = `${this.serverUrl}/v1/txs`;
|
|
1898
|
+
let iterator = null;
|
|
1899
|
+
const closeIterator = () => iterator?.close();
|
|
1900
|
+
const self = this;
|
|
1901
|
+
const gen = (async function* () {
|
|
1902
|
+
const abortHandler = closeIterator;
|
|
1903
|
+
signal?.addEventListener("abort", abortHandler);
|
|
1904
|
+
try {
|
|
1905
|
+
while (!signal?.aborted) {
|
|
1906
|
+
try {
|
|
1907
|
+
const currentIterator = eventSourceIterator(new EventSource(url));
|
|
1908
|
+
iterator = currentIterator;
|
|
1909
|
+
for await (const event of currentIterator) {
|
|
1910
|
+
if (signal?.aborted) break;
|
|
1911
|
+
try {
|
|
1912
|
+
const data = JSON.parse(event.data);
|
|
1913
|
+
const txNotification = self.parseTransactionNotification(data);
|
|
1914
|
+
if (txNotification) {
|
|
1915
|
+
yield txNotification;
|
|
1916
|
+
}
|
|
1917
|
+
} catch (err) {
|
|
1918
|
+
console.error("Failed to parse transaction notification:", err);
|
|
1919
|
+
throw err;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
if (signal?.aborted || error instanceof Error && error.name === "AbortError") {
|
|
1924
|
+
break;
|
|
1925
|
+
}
|
|
1926
|
+
if (isFetchTimeoutError(error)) {
|
|
1927
|
+
console.debug("Timeout error ignored");
|
|
1928
|
+
continue;
|
|
1929
|
+
}
|
|
1930
|
+
if (isEventSourceError(error)) {
|
|
1931
|
+
throw error;
|
|
1932
|
+
}
|
|
1933
|
+
console.error("Transaction stream error:", error);
|
|
1934
|
+
throw error;
|
|
1935
|
+
} finally {
|
|
1936
|
+
closeIterator();
|
|
1937
|
+
iterator = null;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
} finally {
|
|
1941
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
1942
|
+
closeIterator();
|
|
1943
|
+
}
|
|
1944
|
+
})();
|
|
1945
|
+
const origReturn = gen.return.bind(gen);
|
|
1946
|
+
gen.return = (value) => {
|
|
1947
|
+
closeIterator();
|
|
1948
|
+
return origReturn(value);
|
|
1949
|
+
};
|
|
1950
|
+
return gen;
|
|
1951
|
+
}
|
|
1952
|
+
async getPendingTxs(intent) {
|
|
1953
|
+
const url = `${this.serverUrl}/v1/tx/pending`;
|
|
1954
|
+
const response = await this.authedFetch(url, {
|
|
1955
|
+
method: "POST",
|
|
1956
|
+
headers: {
|
|
1957
|
+
"Content-Type": "application/json"
|
|
1958
|
+
},
|
|
1959
|
+
body: JSON.stringify({
|
|
1960
|
+
intent: {
|
|
1961
|
+
proof: intent.proof,
|
|
1962
|
+
message: exports.Intent.encodeMessage(intent.message)
|
|
1963
|
+
}
|
|
1964
|
+
})
|
|
1965
|
+
});
|
|
1966
|
+
if (!response.ok) {
|
|
1967
|
+
const errorText = await response.text();
|
|
1968
|
+
handleError(errorText, `Failed to get pending transactions: ${errorText}`);
|
|
1969
|
+
}
|
|
1970
|
+
const data = await response.json();
|
|
1971
|
+
return data.pendingTxs;
|
|
1972
|
+
}
|
|
1973
|
+
parseSettlementEvent(data) {
|
|
1974
|
+
if (data.batchStarted) {
|
|
1975
|
+
return {
|
|
1976
|
+
type: "batch_started" /* BatchStarted */,
|
|
1977
|
+
id: data.batchStarted.id,
|
|
1978
|
+
intentIdHashes: data.batchStarted.intentIdHashes,
|
|
1979
|
+
batchExpiry: BigInt(data.batchStarted.batchExpiry)
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
if (data.batchFinalization) {
|
|
1983
|
+
return {
|
|
1984
|
+
type: "batch_finalization" /* BatchFinalization */,
|
|
1985
|
+
id: data.batchFinalization.id,
|
|
1986
|
+
commitmentTx: data.batchFinalization.commitmentTx
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
if (data.batchFinalized) {
|
|
1990
|
+
return {
|
|
1991
|
+
type: "batch_finalized" /* BatchFinalized */,
|
|
1992
|
+
id: data.batchFinalized.id,
|
|
1993
|
+
commitmentTxid: data.batchFinalized.commitmentTxid
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
if (data.batchFailed) {
|
|
1997
|
+
return {
|
|
1998
|
+
type: "batch_failed" /* BatchFailed */,
|
|
1999
|
+
id: data.batchFailed.id,
|
|
2000
|
+
reason: data.batchFailed.reason
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
if (data.treeSigningStarted) {
|
|
2004
|
+
return {
|
|
2005
|
+
type: "tree_signing_started" /* TreeSigningStarted */,
|
|
2006
|
+
id: data.treeSigningStarted.id,
|
|
2007
|
+
cosignersPublicKeys: data.treeSigningStarted.cosignersPubkeys,
|
|
2008
|
+
unsignedCommitmentTx: data.treeSigningStarted.unsignedCommitmentTx
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
if (data.treeNoncesAggregated) {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
if (data.treeNonces) {
|
|
2015
|
+
return {
|
|
2016
|
+
type: "tree_nonces" /* TreeNonces */,
|
|
2017
|
+
id: data.treeNonces.id,
|
|
2018
|
+
topic: data.treeNonces.topic,
|
|
2019
|
+
txid: data.treeNonces.txid,
|
|
2020
|
+
nonces: decodeMusig2Nonces(data.treeNonces.nonces)
|
|
2021
|
+
// pubkey -> public nonce
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
if (data.treeTx) {
|
|
2025
|
+
const children = Object.fromEntries(
|
|
2026
|
+
Object.entries(data.treeTx.children).map(([outputIndex, txid]) => {
|
|
2027
|
+
return [parseInt(outputIndex), txid];
|
|
2028
|
+
})
|
|
2029
|
+
);
|
|
2030
|
+
return {
|
|
2031
|
+
type: "tree_tx" /* TreeTx */,
|
|
2032
|
+
id: data.treeTx.id,
|
|
2033
|
+
topic: data.treeTx.topic,
|
|
2034
|
+
batchIndex: data.treeTx.batchIndex,
|
|
2035
|
+
chunk: {
|
|
2036
|
+
txid: data.treeTx.txid,
|
|
2037
|
+
tx: data.treeTx.tx,
|
|
2038
|
+
children
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
if (data.treeSignature) {
|
|
2043
|
+
return {
|
|
2044
|
+
type: "tree_signature" /* TreeSignature */,
|
|
2045
|
+
id: data.treeSignature.id,
|
|
2046
|
+
topic: data.treeSignature.topic,
|
|
2047
|
+
batchIndex: data.treeSignature.batchIndex,
|
|
2048
|
+
txid: data.treeSignature.txid,
|
|
2049
|
+
signature: data.treeSignature.signature
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
if (data.streamStarted) {
|
|
2053
|
+
return {
|
|
2054
|
+
type: "stream_started" /* StreamStarted */,
|
|
2055
|
+
id: data.streamStarted.id
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
if (data.heartbeat) {
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
console.warn("Unknown event type:", data);
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
parseTransactionNotification(data) {
|
|
2065
|
+
if (data.commitmentTx) {
|
|
2066
|
+
return {
|
|
2067
|
+
commitmentTx: {
|
|
2068
|
+
txid: data.commitmentTx.txid,
|
|
2069
|
+
tx: data.commitmentTx.tx,
|
|
2070
|
+
spentVtxos: data.commitmentTx.spentVtxos.map(mapVtxo),
|
|
2071
|
+
spendableVtxos: data.commitmentTx.spendableVtxos.map(mapVtxo),
|
|
2072
|
+
checkpointTxs: data.commitmentTx.checkpointTxs
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
if (data.arkTx) {
|
|
2077
|
+
return {
|
|
2078
|
+
arkTx: {
|
|
2079
|
+
txid: data.arkTx.txid,
|
|
2080
|
+
tx: data.arkTx.tx,
|
|
2081
|
+
spentVtxos: data.arkTx.spentVtxos.map(mapVtxo),
|
|
2082
|
+
spendableVtxos: data.arkTx.spendableVtxos.map(mapVtxo),
|
|
2083
|
+
checkpointTxs: data.arkTx.checkpointTxs
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
if (data.heartbeat) {
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
console.warn("Unknown transaction notification type:", data);
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
function encodeMusig2Nonces(nonces) {
|
|
2095
|
+
const noncesObject = {};
|
|
2096
|
+
for (const [txid, nonce] of nonces) {
|
|
2097
|
+
noncesObject[txid] = base.hex.encode(nonce.pubNonce);
|
|
2098
|
+
}
|
|
2099
|
+
return noncesObject;
|
|
2100
|
+
}
|
|
2101
|
+
function encodeMusig2Signatures(signatures) {
|
|
2102
|
+
const sigObject = {};
|
|
2103
|
+
for (const [txid, sig] of signatures) {
|
|
2104
|
+
sigObject[txid] = base.hex.encode(sig.encode());
|
|
2105
|
+
}
|
|
2106
|
+
return sigObject;
|
|
2107
|
+
}
|
|
2108
|
+
function decodeMusig2Nonces(noncesObject) {
|
|
2109
|
+
return new Map(
|
|
2110
|
+
Object.entries(noncesObject).map(([txid, nonce]) => {
|
|
2111
|
+
if (typeof nonce !== "string") {
|
|
2112
|
+
throw new Error("invalid nonce");
|
|
2113
|
+
}
|
|
2114
|
+
return [txid, { pubNonce: base.hex.decode(nonce) }];
|
|
2115
|
+
})
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
function isFetchTimeoutError(err) {
|
|
2119
|
+
const checkError = (error) => {
|
|
2120
|
+
if (!(error instanceof Error)) return false;
|
|
2121
|
+
const isCloudflare524 = error.name === "TypeError" && error.message === "Failed to fetch";
|
|
2122
|
+
return isCloudflare524 || error.name === "HeadersTimeoutError" || error.name === "BodyTimeoutError" || error.code === "UND_ERR_HEADERS_TIMEOUT" || error.code === "UND_ERR_BODY_TIMEOUT";
|
|
2123
|
+
};
|
|
2124
|
+
return checkError(err) || checkError(err.cause);
|
|
2125
|
+
}
|
|
2126
|
+
function mapVtxo(vtxo) {
|
|
2127
|
+
return {
|
|
2128
|
+
outpoint: {
|
|
2129
|
+
txid: vtxo.outpoint.txid,
|
|
2130
|
+
vout: vtxo.outpoint.vout
|
|
2131
|
+
},
|
|
2132
|
+
amount: vtxo.amount,
|
|
2133
|
+
script: vtxo.script,
|
|
2134
|
+
createdAt: vtxo.createdAt,
|
|
2135
|
+
expiresAt: vtxo.expiresAt,
|
|
2136
|
+
commitmentTxids: vtxo.commitmentTxids,
|
|
2137
|
+
isPreconfirmed: vtxo.isPreconfirmed,
|
|
2138
|
+
isSwept: vtxo.isSwept,
|
|
2139
|
+
isUnrolled: vtxo.isUnrolled,
|
|
2140
|
+
isSpent: vtxo.isSpent,
|
|
2141
|
+
spentBy: vtxo.spentBy,
|
|
2142
|
+
settledBy: vtxo.settledBy,
|
|
2143
|
+
arkTxid: vtxo.arkTxid
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
function handleError(errorText, defaultMessage) {
|
|
2147
|
+
const error = new Error(errorText);
|
|
2148
|
+
const arkError = maybeArkError(error);
|
|
2149
|
+
throw arkError ?? new Error(defaultMessage);
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/providers/availability.ts
|
|
2153
|
+
function isRetryableProviderError(error) {
|
|
2154
|
+
return error instanceof ProviderUnavailableError || error instanceof FetchError || isFetchTimeoutError(error);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// src/contracts/constants.ts
|
|
2158
|
+
var DEFAULT_PAGE_SIZE = 500;
|
|
2159
|
+
var SCRIPT_QUERY_CHUNK_SIZE = 32;
|
|
2160
|
+
var OUTPOINT_QUERY_CHUNK_SIZE = 64;
|
|
2161
|
+
|
|
2162
|
+
// src/wallet/vtxo.ts
|
|
2163
|
+
var EXPIRY_MIN_PLAUSIBLE_MS = Date.UTC(2025, 0, 1);
|
|
2164
|
+
async function resolveTimeHeight(provider) {
|
|
2165
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
2166
|
+
if (!provider) return { timestamp };
|
|
2167
|
+
try {
|
|
2168
|
+
const tip = await provider.getChainTip();
|
|
2169
|
+
return { timestamp, height: tip.height };
|
|
2170
|
+
} catch (e) {
|
|
2171
|
+
console.warn("Failed to fetch chain tip; height-based expiry will not be evaluated", e);
|
|
2172
|
+
return { timestamp };
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
function parseWireExpiry(raw) {
|
|
2176
|
+
if (raw === null || raw === void 0 || raw === "") return {};
|
|
2177
|
+
const n = Number(raw);
|
|
2178
|
+
if (!Number.isFinite(n) || n <= 0) return {};
|
|
2179
|
+
const ms = n * 1e3;
|
|
2180
|
+
if (ms >= EXPIRY_MIN_PLAUSIBLE_MS) return { expiresAt: new Date(ms) };
|
|
2181
|
+
return { expiresAtHeight: n };
|
|
2182
|
+
}
|
|
2183
|
+
function toBatchExpiry(c) {
|
|
2184
|
+
if (c.expiresAt !== void 0) return c.expiresAt.getTime();
|
|
2185
|
+
if (c.expiresAtHeight !== void 0) return c.expiresAtHeight * 1e3;
|
|
2186
|
+
return void 0;
|
|
2187
|
+
}
|
|
2188
|
+
function parseLegacyExpiry(batchExpiry) {
|
|
2189
|
+
if (batchExpiry === void 0 || batchExpiry <= 0) return {};
|
|
2190
|
+
if (batchExpiry >= EXPIRY_MIN_PLAUSIBLE_MS) return { expiresAt: new Date(batchExpiry) };
|
|
2191
|
+
return { expiresAtHeight: batchExpiry / 1e3 };
|
|
2192
|
+
}
|
|
2193
|
+
function toVirtualStatus(c) {
|
|
2194
|
+
return {
|
|
2195
|
+
// Precedence is load-bearing: consumers bucket on this label, so any other order silently
|
|
2196
|
+
// moves VTXOs between buckets.
|
|
2197
|
+
state: c.isSpent ? "spent" : c.isSwept ? "swept" : c.isPreconfirmed ? "preconfirmed" : "settled",
|
|
2198
|
+
commitmentTxIds: c.commitmentTxIds,
|
|
2199
|
+
batchExpiry: toBatchExpiry(c)
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
function normalizeVtxo(v) {
|
|
2203
|
+
const state = v.virtualStatus?.state;
|
|
2204
|
+
const canonicalExpiry = v.expiresAt !== void 0 || v.expiresAtHeight !== void 0;
|
|
2205
|
+
const expiry = canonicalExpiry ? (
|
|
2206
|
+
// Coerce: a backend that persists through JSON hands back an ISO string, which typechecks
|
|
2207
|
+
// as `Date` but returns NaN from `.getTime()` — comparing false against everything.
|
|
2208
|
+
{
|
|
2209
|
+
expiresAt: v.expiresAt === void 0 ? void 0 : new Date(v.expiresAt),
|
|
2210
|
+
expiresAtHeight: v.expiresAtHeight
|
|
2211
|
+
}
|
|
2212
|
+
) : parseLegacyExpiry(v.virtualStatus?.batchExpiry);
|
|
2213
|
+
const isSpent = v.isSpent ?? state === "spent";
|
|
2214
|
+
const isSwept = v.isSwept ?? state === "swept";
|
|
2215
|
+
const isPreconfirmed = v.isPreconfirmed ?? state === "preconfirmed";
|
|
2216
|
+
const commitmentTxIds = v.commitmentTxIds ?? v.virtualStatus?.commitmentTxIds ?? [];
|
|
2217
|
+
return {
|
|
2218
|
+
...v,
|
|
2219
|
+
isSpent,
|
|
2220
|
+
isSwept,
|
|
2221
|
+
isPreconfirmed,
|
|
2222
|
+
spentBy: v.spentBy ?? "",
|
|
2223
|
+
commitmentTxIds,
|
|
2224
|
+
...expiry,
|
|
2225
|
+
virtualStatus: v.virtualStatus ?? toVirtualStatus({ isSpent, isSwept, isPreconfirmed, commitmentTxIds, ...expiry })
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function convertVtxo(vtxo) {
|
|
2229
|
+
const expiry = parseWireExpiry(vtxo.expiresAt);
|
|
2230
|
+
const facts = {
|
|
2231
|
+
isSpent: vtxo.isSpent,
|
|
2232
|
+
isSwept: vtxo.isSwept,
|
|
2233
|
+
isPreconfirmed: vtxo.isPreconfirmed,
|
|
2234
|
+
commitmentTxIds: vtxo.commitmentTxids,
|
|
2235
|
+
...expiry
|
|
2236
|
+
};
|
|
2237
|
+
return {
|
|
2238
|
+
txid: vtxo.outpoint.txid,
|
|
2239
|
+
vout: vtxo.outpoint.vout,
|
|
2240
|
+
value: Number(vtxo.amount),
|
|
2241
|
+
status: {
|
|
2242
|
+
confirmed: !vtxo.isSwept && !vtxo.isPreconfirmed,
|
|
2243
|
+
isLeaf: !vtxo.isPreconfirmed
|
|
2244
|
+
},
|
|
2245
|
+
...facts,
|
|
2246
|
+
virtualStatus: toVirtualStatus(facts),
|
|
2247
|
+
spentBy: vtxo.spentBy ?? "",
|
|
2248
|
+
settledBy: vtxo.settledBy,
|
|
2249
|
+
arkTxId: vtxo.arkTxid,
|
|
2250
|
+
createdAt: new Date(Number(vtxo.createdAt) * 1e3),
|
|
2251
|
+
isUnrolled: vtxo.isUnrolled,
|
|
2252
|
+
script: vtxo.script,
|
|
2253
|
+
assets: vtxo.assets?.map((a) => ({
|
|
2254
|
+
assetId: a.assetId,
|
|
2255
|
+
amount: BigInt(a.amount)
|
|
2256
|
+
}))
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
async function getNormalizedVtxos(provider, opts) {
|
|
2260
|
+
const { vtxos, page } = await provider.getVtxos(opts);
|
|
2261
|
+
return { vtxos: vtxos.map(normalizeVtxo), page };
|
|
2262
|
+
}
|
|
2263
|
+
async function getAllNormalizedVtxos(provider, scripts, opts = {}) {
|
|
2264
|
+
const { pageSize = DEFAULT_PAGE_SIZE, ...filters } = opts;
|
|
2265
|
+
const all = [];
|
|
2266
|
+
for (let i = 0; i < scripts.length; i += SCRIPT_QUERY_CHUNK_SIZE) {
|
|
2267
|
+
const chunk = scripts.slice(i, i + SCRIPT_QUERY_CHUNK_SIZE);
|
|
2268
|
+
let pageIndex = 0;
|
|
2269
|
+
let hasMore = true;
|
|
2270
|
+
while (hasMore) {
|
|
2271
|
+
const { vtxos, page } = await getNormalizedVtxos(provider, {
|
|
2272
|
+
...filters,
|
|
2273
|
+
scripts: chunk,
|
|
2274
|
+
pageIndex,
|
|
2275
|
+
pageSize
|
|
2276
|
+
});
|
|
2277
|
+
all.push(...vtxos);
|
|
2278
|
+
hasMore = page ? vtxos.length === pageSize : false;
|
|
2279
|
+
pageIndex++;
|
|
2280
|
+
if (hasMore) await new Promise((r) => setTimeout(r, 500));
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
return all;
|
|
2284
|
+
}
|
|
2285
|
+
async function fetchVtxoCreatedAtByTxid(provider, txids) {
|
|
2286
|
+
const unique = [...new Set(txids)].filter((txid) => txid !== "");
|
|
2287
|
+
const createdAt = /* @__PURE__ */ new Map();
|
|
2288
|
+
for (let i = 0; i < unique.length; i += OUTPOINT_QUERY_CHUNK_SIZE) {
|
|
2289
|
+
const chunk = unique.slice(i, i + OUTPOINT_QUERY_CHUNK_SIZE);
|
|
2290
|
+
try {
|
|
2291
|
+
const { vtxos } = await getNormalizedVtxos(provider, {
|
|
2292
|
+
outpoints: chunk.map((txid) => ({ txid, vout: 0 })),
|
|
2293
|
+
pageSize: DEFAULT_PAGE_SIZE
|
|
2294
|
+
});
|
|
2295
|
+
for (const v of vtxos) {
|
|
2296
|
+
createdAt.set(v.txid, v.createdAt.getTime());
|
|
2297
|
+
}
|
|
2298
|
+
} catch (err) {
|
|
2299
|
+
if (!isRetryableProviderError(err)) throw err;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return createdAt;
|
|
2303
|
+
}
|
|
2304
|
+
function hasTerminalSpend(vtxo) {
|
|
2305
|
+
const n = normalizeVtxo(vtxo);
|
|
2306
|
+
return !!n.isSpent || !!n.spentBy || !!n.settledBy;
|
|
2307
|
+
}
|
|
2308
|
+
function isPastExpiry(vtxo, now) {
|
|
2309
|
+
const n = normalizeVtxo(vtxo);
|
|
2310
|
+
if (n.expiresAt !== void 0 && now.timestamp.getTime() >= n.expiresAt.getTime()) return true;
|
|
2311
|
+
return n.expiresAtHeight !== void 0 && now.height !== void 0 && now.height >= n.expiresAtHeight;
|
|
2312
|
+
}
|
|
2313
|
+
function canSpendOffchain(vtxo, now) {
|
|
2314
|
+
const n = normalizeVtxo(vtxo);
|
|
2315
|
+
return !hasTerminalSpend(n) && !(n.isSwept || isPastExpiry(n, now));
|
|
2316
|
+
}
|
|
2317
|
+
function canRecoverOnchain(vtxo, now) {
|
|
2318
|
+
const n = normalizeVtxo(vtxo);
|
|
2319
|
+
return !hasTerminalSpend(n) && (n.isSwept || isPastExpiry(n, now));
|
|
2320
|
+
}
|
|
2321
|
+
function toOffchainInputFeeParams(vtxo) {
|
|
2322
|
+
const batchExpiry = toBatchExpiry(vtxo);
|
|
2323
|
+
return {
|
|
2324
|
+
amount: BigInt(vtxo.value),
|
|
2325
|
+
type: vtxo.isSwept ? "recoverable" : "vtxo",
|
|
2326
|
+
weight: 0,
|
|
2327
|
+
birth: vtxo.createdAt,
|
|
2328
|
+
expiry: batchExpiry ? new Date(batchExpiry) : void 0
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
function isVirtualCoin(input) {
|
|
2332
|
+
return typeof input === "object" && input !== null && typeof input.script === "string";
|
|
2333
|
+
}
|
|
2334
|
+
function isSpendable(vtxo) {
|
|
2335
|
+
return !hasTerminalSpend(vtxo);
|
|
2336
|
+
}
|
|
2337
|
+
function isRecoverable(vtxo) {
|
|
2338
|
+
const n = normalizeVtxo(vtxo);
|
|
2339
|
+
return n.isSwept && !hasTerminalSpend(n);
|
|
2340
|
+
}
|
|
2341
|
+
function isExpired(vtxo) {
|
|
2342
|
+
const n = normalizeVtxo(vtxo);
|
|
2343
|
+
return n.isSwept || n.expiresAt !== void 0 && n.expiresAt.getTime() <= Date.now();
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
exports.ArkAddress = ArkAddress;
|
|
2347
|
+
exports.ArkError = ArkError;
|
|
2348
|
+
exports.ArkErrorName = ArkErrorName;
|
|
2349
|
+
exports.ArkPsbtFieldKey = ArkPsbtFieldKey;
|
|
2350
|
+
exports.ArkPsbtFieldKeyType = ArkPsbtFieldKeyType;
|
|
2351
|
+
exports.ConditionWitness = ConditionWitness;
|
|
2352
|
+
exports.CosignerPublicKey = CosignerPublicKey;
|
|
2353
|
+
exports.DEFAULT_ARKADE_SERVER_URL = DEFAULT_ARKADE_SERVER_URL;
|
|
2354
|
+
exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
|
|
2355
|
+
exports.DEFAULT_NETWORK_NAME = DEFAULT_NETWORK_NAME;
|
|
2356
|
+
exports.DEFAULT_PAGE_SIZE = DEFAULT_PAGE_SIZE;
|
|
2357
|
+
exports.DigestMismatchError = DigestMismatchError;
|
|
2358
|
+
exports.FetchError = FetchError;
|
|
2359
|
+
exports.OP_RETURN_EMPTY_PKSCRIPT = OP_RETURN_EMPTY_PKSCRIPT;
|
|
2360
|
+
exports.PrevArkTxField = PrevArkTxField;
|
|
2361
|
+
exports.PrevoutTxField = PrevoutTxField;
|
|
2362
|
+
exports.ProviderUnavailableError = ProviderUnavailableError;
|
|
2363
|
+
exports.RestArkProvider = RestArkProvider;
|
|
2364
|
+
exports.SCRIPT_QUERY_CHUNK_SIZE = SCRIPT_QUERY_CHUNK_SIZE;
|
|
2365
|
+
exports.ServerResponseMismatchError = ServerResponseMismatchError;
|
|
2366
|
+
exports.SettlementEventType = SettlementEventType;
|
|
2367
|
+
exports.TapTreeCoder = TapTreeCoder;
|
|
2368
|
+
exports.Transaction = Transaction;
|
|
2369
|
+
exports.VtxoScript = VtxoScript;
|
|
2370
|
+
exports.VtxoTaprootTree = VtxoTaprootTree;
|
|
2371
|
+
exports.VtxoTreeExpiry = VtxoTreeExpiry;
|
|
2372
|
+
exports.assembleBtcdTaprootTree = assembleBtcdTaprootTree;
|
|
2373
|
+
exports.baseFetch = baseFetch;
|
|
2374
|
+
exports.buildVersion = buildVersion;
|
|
2375
|
+
exports.canRecoverOnchain = canRecoverOnchain;
|
|
2376
|
+
exports.canSpendOffchain = canSpendOffchain;
|
|
2377
|
+
exports.convertVtxo = convertVtxo;
|
|
2378
|
+
exports.craftToSpendTx = craftToSpendTx;
|
|
2379
|
+
exports.decodeTapscript = decodeTapscript;
|
|
2380
|
+
exports.eventSourceIterator = eventSourceIterator;
|
|
2381
|
+
exports.fetch = fetch;
|
|
2382
|
+
exports.fetchVtxoCreatedAtByTxid = fetchVtxoCreatedAtByTxid;
|
|
2383
|
+
exports.getAllNormalizedVtxos = getAllNormalizedVtxos;
|
|
2384
|
+
exports.getArkPsbtFields = getArkPsbtFields;
|
|
2385
|
+
exports.getNetwork = getNetwork;
|
|
2386
|
+
exports.getNormalizedVtxos = getNormalizedVtxos;
|
|
2387
|
+
exports.getSequence = getSequence;
|
|
2388
|
+
exports.hasTerminalSpend = hasTerminalSpend;
|
|
2389
|
+
exports.isArkError = isArkError;
|
|
2390
|
+
exports.isEventSourceError = isEventSourceError;
|
|
2391
|
+
exports.isExpired = isExpired;
|
|
2392
|
+
exports.isFetchTimeoutError = isFetchTimeoutError;
|
|
2393
|
+
exports.isPastExpiry = isPastExpiry;
|
|
2394
|
+
exports.isRecoverable = isRecoverable;
|
|
2395
|
+
exports.isRetryableProviderError = isRetryableProviderError;
|
|
2396
|
+
exports.isSpendable = isSpendable;
|
|
2397
|
+
exports.isVirtualCoin = isVirtualCoin;
|
|
2398
|
+
exports.maybeArkError = maybeArkError;
|
|
2399
|
+
exports.networks = networks;
|
|
2400
|
+
exports.normalizeVtxo = normalizeVtxo;
|
|
2401
|
+
exports.parseLegacyExpiry = parseLegacyExpiry;
|
|
2402
|
+
exports.rateGate = rateGate;
|
|
2403
|
+
exports.resolveTimeHeight = resolveTimeHeight;
|
|
2404
|
+
exports.scriptFromTapLeafScript = scriptFromTapLeafScript;
|
|
2405
|
+
exports.sdkVersion = sdkVersion;
|
|
2406
|
+
exports.sequenceToTimelock = sequenceToTimelock;
|
|
2407
|
+
exports.setArkPsbtField = setArkPsbtField;
|
|
2408
|
+
exports.throwIfHttpUnavailable = throwIfHttpUnavailable;
|
|
2409
|
+
exports.timelockToSequence = timelockToSequence;
|
|
2410
|
+
exports.toBatchExpiry = toBatchExpiry;
|
|
2411
|
+
exports.toOffchainInputFeeParams = toOffchainInputFeeParams;
|
|
2412
|
+
exports.toProviderUnavailable = toProviderUnavailable;
|
|
2413
|
+
exports.toVirtualStatus = toVirtualStatus;
|
|
2414
|
+
//# sourceMappingURL=chunk-OWCPXRGP.cjs.map
|
|
2415
|
+
//# sourceMappingURL=chunk-OWCPXRGP.cjs.map
|