@arkade-os/sdk 0.4.34 → 0.4.36
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 +4 -4
- package/dist/adapters/expo.d.cts +2 -2
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +2 -2
- package/dist/adapters/indexedDB.cjs +3 -3
- package/dist/adapters/indexedDB.js +2 -2
- package/dist/{ark-Dsv5Jq4E.d.cts → ark-D6sau_6-.d.cts} +458 -3
- package/dist/{ark-Dsv5Jq4E.d.ts → ark-D6sau_6-.d.ts} +458 -3
- package/dist/{asyncStorageTaskQueue-D92ch8yI.d.cts → asyncStorageTaskQueue-CpC027t_.d.cts} +2 -2
- package/dist/{asyncStorageTaskQueue-BH-zuth5.d.ts → asyncStorageTaskQueue-GT8fmPUG.d.ts} +2 -2
- package/dist/{chunk-FXFBPXV3.js → chunk-2JJKX2RK.js} +139 -41
- package/dist/chunk-2JJKX2RK.js.map +1 -0
- package/dist/{chunk-CCLNFHJ5.cjs → chunk-2XE5BSIY.cjs} +16 -10
- package/dist/chunk-2XE5BSIY.cjs.map +1 -0
- package/dist/{chunk-ZS3OZHC7.cjs → chunk-A5PY4NBP.cjs} +7 -7
- package/dist/{chunk-ZS3OZHC7.cjs.map → chunk-A5PY4NBP.cjs.map} +1 -1
- package/dist/{chunk-FSAXPBGP.cjs → chunk-C6OODRWD.cjs} +143 -40
- package/dist/chunk-C6OODRWD.cjs.map +1 -0
- package/dist/{chunk-HFXEUW55.js → chunk-HBPKIIMN.js} +1472 -202
- package/dist/chunk-HBPKIIMN.js.map +1 -0
- package/dist/{chunk-5WDBHWX3.js → chunk-KZV3FJJR.js} +10 -4
- package/dist/chunk-KZV3FJJR.js.map +1 -0
- package/dist/{chunk-XCHBQVMK.cjs → chunk-TUSGEWOX.cjs} +1540 -265
- package/dist/chunk-TUSGEWOX.cjs.map +1 -0
- package/dist/{chunk-VVGD3JIP.js → chunk-Z2VRVZW4.js} +3 -3
- package/dist/{chunk-VVGD3JIP.js.map → chunk-Z2VRVZW4.js.map} +1 -1
- package/dist/contracts/handlers/index.d.cts +3 -3
- package/dist/contracts/handlers/index.d.ts +3 -3
- package/dist/{delegate-BaS5SCIW.d.cts → delegate-C-L6gSZx.d.cts} +1 -1
- package/dist/{delegate-Baz_hb83.d.ts → delegate-De5__fpZ.d.ts} +1 -1
- package/dist/{index-FwXZveaX.d.ts → index-BETdjE_o.d.ts} +2 -2
- package/dist/{index-lNZ6qaO3.d.cts → index-jwQfHP6D.d.cts} +2 -2
- package/dist/index.cjs +129 -105
- package/dist/index.d.cts +69 -9
- package/dist/index.d.ts +69 -9
- package/dist/index.js +2 -2
- package/dist/repositories/realm/index.cjs +12 -12
- package/dist/repositories/realm/index.cjs.map +1 -1
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/realm/index.js +3 -3
- package/dist/repositories/realm/index.js.map +1 -1
- package/dist/repositories/sqlite/index.cjs +11 -11
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.js +2 -2
- package/dist/{taskRunner-vFRA3F9b.d.cts → taskRunner-DCyp6Gea.d.cts} +2 -2
- package/dist/{taskRunner-B1NUWyWR.d.ts → taskRunner-DnxtObeq.d.ts} +2 -2
- package/dist/wallet/expo/background.cjs +12 -12
- package/dist/wallet/expo/background.d.cts +3 -3
- package/dist/wallet/expo/background.d.ts +3 -3
- package/dist/wallet/expo/background.js +4 -4
- package/dist/wallet/expo/index.cjs +11 -11
- package/dist/wallet/expo/index.d.cts +4 -4
- package/dist/wallet/expo/index.d.ts +4 -4
- package/dist/wallet/expo/index.js +3 -3
- package/dist/{wallet-D6uoBLmS.d.ts → wallet-BWHbd5b1.d.cts} +231 -8
- package/dist/{wallet-By9HIo0Q.d.cts → wallet-Bth5uucA.d.ts} +231 -8
- package/dist/worker/expo/index.cjs +7 -7
- package/dist/worker/expo/index.d.cts +4 -4
- package/dist/worker/expo/index.d.ts +4 -4
- package/dist/worker/expo/index.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-5WDBHWX3.js.map +0 -1
- package/dist/chunk-CCLNFHJ5.cjs.map +0 -1
- package/dist/chunk-FSAXPBGP.cjs.map +0 -1
- package/dist/chunk-FXFBPXV3.js.map +0 -1
- package/dist/chunk-HFXEUW55.js.map +0 -1
- package/dist/chunk-XCHBQVMK.cjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkC6OODRWD_cjs = require('./chunk-C6OODRWD.cjs');
|
|
4
4
|
var chunkGUTKJMSF_cjs = require('./chunk-GUTKJMSF.cjs');
|
|
5
5
|
var chunkCMPJR3HS_cjs = require('./chunk-CMPJR3HS.cjs');
|
|
6
6
|
var utils_js = require('@scure/btc-signer/utils.js');
|
|
@@ -181,7 +181,7 @@ var TreeSignerSession = class _TreeSignerSession {
|
|
|
181
181
|
noncesByPubkey.set(base.hex.encode(myPublicKey.subarray(1)), myNonce);
|
|
182
182
|
const tx = this.graph.find(txid);
|
|
183
183
|
if (!tx) throw new Error(`missing tx for txid ${txid}`);
|
|
184
|
-
const cosigners =
|
|
184
|
+
const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(tx.root, 0, chunkC6OODRWD_cjs.CosignerPublicKey).map(
|
|
185
185
|
(c) => base.hex.encode(c.key.subarray(1))
|
|
186
186
|
// xonly pubkey
|
|
187
187
|
);
|
|
@@ -233,7 +233,7 @@ var TreeSignerSession = class _TreeSignerSession {
|
|
|
233
233
|
if (!aggNonce) throw new Error("missing aggregate nonce");
|
|
234
234
|
const prevoutAmounts = [];
|
|
235
235
|
const prevoutScripts = [];
|
|
236
|
-
const cosigners =
|
|
236
|
+
const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(g.root, 0, chunkC6OODRWD_cjs.CosignerPublicKey).map((c) => c.key);
|
|
237
237
|
const { finalKey } = aggregateKeys(cosigners, true, {
|
|
238
238
|
taprootTweak: this.scriptRoot
|
|
239
239
|
});
|
|
@@ -980,14 +980,14 @@ var RestDelegateProvider = class {
|
|
|
980
980
|
*/
|
|
981
981
|
async delegate(intent, forfeitTxs, options) {
|
|
982
982
|
const url = `${this.url}/v1/delegate`;
|
|
983
|
-
const response = await
|
|
983
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(url, {
|
|
984
984
|
method: "POST",
|
|
985
985
|
headers: {
|
|
986
986
|
"Content-Type": "application/json"
|
|
987
987
|
},
|
|
988
988
|
body: JSON.stringify({
|
|
989
989
|
intent: {
|
|
990
|
-
message:
|
|
990
|
+
message: chunkC6OODRWD_cjs.Intent.encodeMessage(intent.message),
|
|
991
991
|
proof: intent.proof
|
|
992
992
|
},
|
|
993
993
|
forfeit_txs: forfeitTxs,
|
|
@@ -995,8 +995,8 @@ var RestDelegateProvider = class {
|
|
|
995
995
|
})
|
|
996
996
|
});
|
|
997
997
|
if (!response.ok) {
|
|
998
|
-
const
|
|
999
|
-
throw new Error(`Failed to delegate: ${
|
|
998
|
+
const errorText2 = await response.text();
|
|
999
|
+
throw new Error(`Failed to delegate: ${errorText2}`);
|
|
1000
1000
|
}
|
|
1001
1001
|
}
|
|
1002
1002
|
/**
|
|
@@ -1007,10 +1007,10 @@ var RestDelegateProvider = class {
|
|
|
1007
1007
|
*/
|
|
1008
1008
|
async getDelegateInfo() {
|
|
1009
1009
|
const url = `${this.url}/v1/delegator/info`;
|
|
1010
|
-
const response = await
|
|
1010
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(url);
|
|
1011
1011
|
if (!response.ok) {
|
|
1012
|
-
const
|
|
1013
|
-
throw new Error(`Failed to get delegate info: ${
|
|
1012
|
+
const errorText2 = await response.text();
|
|
1013
|
+
throw new Error(`Failed to get delegate info: ${errorText2}`);
|
|
1014
1014
|
}
|
|
1015
1015
|
const data = await response.json();
|
|
1016
1016
|
if (!isDelegateInfo(data)) {
|
|
@@ -1042,14 +1042,14 @@ var EsploraProvider = class {
|
|
|
1042
1042
|
pollingInterval;
|
|
1043
1043
|
forcePolling;
|
|
1044
1044
|
async getCoins(address) {
|
|
1045
|
-
const response = await
|
|
1045
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/address/${address}/utxo`);
|
|
1046
1046
|
if (!response.ok) {
|
|
1047
1047
|
throw new Error(`Failed to fetch UTXOs: ${response.statusText}`);
|
|
1048
1048
|
}
|
|
1049
1049
|
return response.json();
|
|
1050
1050
|
}
|
|
1051
1051
|
async getFeeRate() {
|
|
1052
|
-
const response = await
|
|
1052
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/fee-estimates`);
|
|
1053
1053
|
if (response.status === 404) {
|
|
1054
1054
|
return void 0;
|
|
1055
1055
|
}
|
|
@@ -1070,7 +1070,7 @@ var EsploraProvider = class {
|
|
|
1070
1070
|
}
|
|
1071
1071
|
}
|
|
1072
1072
|
async getTxOutspends(txid) {
|
|
1073
|
-
const response = await
|
|
1073
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/outspends`);
|
|
1074
1074
|
if (!response.ok) {
|
|
1075
1075
|
const error = await response.text();
|
|
1076
1076
|
throw new Error(`Failed to get transaction outspends: ${error}`);
|
|
@@ -1078,7 +1078,7 @@ var EsploraProvider = class {
|
|
|
1078
1078
|
return response.json();
|
|
1079
1079
|
}
|
|
1080
1080
|
async getTransactions(address) {
|
|
1081
|
-
const response = await
|
|
1081
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/address/${address}/txs`);
|
|
1082
1082
|
if (!response.ok) {
|
|
1083
1083
|
const error = await response.text();
|
|
1084
1084
|
throw new Error(`Failed to get transactions: ${error}`);
|
|
@@ -1086,7 +1086,7 @@ var EsploraProvider = class {
|
|
|
1086
1086
|
return response.json();
|
|
1087
1087
|
}
|
|
1088
1088
|
async getTxStatus(txid) {
|
|
1089
|
-
const txresponse = await
|
|
1089
|
+
const txresponse = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/tx/${txid}`);
|
|
1090
1090
|
if (!txresponse.ok) {
|
|
1091
1091
|
throw new Error(txresponse.statusText);
|
|
1092
1092
|
}
|
|
@@ -1094,7 +1094,7 @@ var EsploraProvider = class {
|
|
|
1094
1094
|
if (!tx.status.confirmed) {
|
|
1095
1095
|
return { confirmed: false };
|
|
1096
1096
|
}
|
|
1097
|
-
const response = await
|
|
1097
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/status`);
|
|
1098
1098
|
if (!response.ok) {
|
|
1099
1099
|
throw new Error(`Failed to get transaction status: ${response.statusText}`);
|
|
1100
1100
|
}
|
|
@@ -1178,7 +1178,7 @@ var EsploraProvider = class {
|
|
|
1178
1178
|
return stopFunc;
|
|
1179
1179
|
}
|
|
1180
1180
|
async getChainTip() {
|
|
1181
|
-
const tipBlocks = await
|
|
1181
|
+
const tipBlocks = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/blocks`);
|
|
1182
1182
|
if (!tipBlocks.ok) {
|
|
1183
1183
|
throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
|
|
1184
1184
|
}
|
|
@@ -1197,7 +1197,7 @@ var EsploraProvider = class {
|
|
|
1197
1197
|
};
|
|
1198
1198
|
}
|
|
1199
1199
|
async broadcastPackage(parent, child) {
|
|
1200
|
-
const response = await
|
|
1200
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/txs/package`, {
|
|
1201
1201
|
method: "POST",
|
|
1202
1202
|
headers: {
|
|
1203
1203
|
"Content-Type": "application/json"
|
|
@@ -1211,7 +1211,7 @@ var EsploraProvider = class {
|
|
|
1211
1211
|
return response.json();
|
|
1212
1212
|
}
|
|
1213
1213
|
async broadcastTx(tx) {
|
|
1214
|
-
const response = await
|
|
1214
|
+
const response = await chunkC6OODRWD_cjs.baseFetch(`${this.baseUrl}/tx`, {
|
|
1215
1215
|
method: "POST",
|
|
1216
1216
|
headers: {
|
|
1217
1217
|
"Content-Type": "text/plain"
|
|
@@ -1280,7 +1280,7 @@ function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
|
1280
1280
|
);
|
|
1281
1281
|
}
|
|
1282
1282
|
function buildForfeitTxWithOutput(inputs, output, txLocktime) {
|
|
1283
|
-
const tx = new
|
|
1283
|
+
const tx = new chunkC6OODRWD_cjs.Transaction({
|
|
1284
1284
|
version: 3,
|
|
1285
1285
|
lockTime: txLocktime
|
|
1286
1286
|
});
|
|
@@ -1356,7 +1356,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
|
|
|
1356
1356
|
if (previousScriptKey.length !== 32) {
|
|
1357
1357
|
throw new Error(`parent output ${childIndex} has invalid script`);
|
|
1358
1358
|
}
|
|
1359
|
-
const cosigners =
|
|
1359
|
+
const cosigners = chunkC6OODRWD_cjs.getArkPsbtFields(child.root, 0, chunkC6OODRWD_cjs.CosignerPublicKey);
|
|
1360
1360
|
if (cosigners.length === 0) {
|
|
1361
1361
|
throw ErrMissingCosignersPublicKeys;
|
|
1362
1362
|
}
|
|
@@ -1456,7 +1456,7 @@ var Extension = class _Extension {
|
|
|
1456
1456
|
`expected magic prefix ${base.hex.encode(ARKADE_MAGIC)}, got ${base.hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
|
|
1457
1457
|
);
|
|
1458
1458
|
}
|
|
1459
|
-
const reader = new
|
|
1459
|
+
const reader = new chunkC6OODRWD_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
|
|
1460
1460
|
const packets = [];
|
|
1461
1461
|
while (reader.remaining() > 0) {
|
|
1462
1462
|
const packetType = reader.readByte();
|
|
@@ -1530,7 +1530,7 @@ var Extension = class _Extension {
|
|
|
1530
1530
|
*/
|
|
1531
1531
|
getAssetPacket() {
|
|
1532
1532
|
for (const p of this.packets) {
|
|
1533
|
-
if (p instanceof
|
|
1533
|
+
if (p instanceof chunkC6OODRWD_cjs.Packet) {
|
|
1534
1534
|
return p;
|
|
1535
1535
|
}
|
|
1536
1536
|
}
|
|
@@ -1538,8 +1538,8 @@ var Extension = class _Extension {
|
|
|
1538
1538
|
}
|
|
1539
1539
|
};
|
|
1540
1540
|
function parsePacket(packetType, data) {
|
|
1541
|
-
if (packetType ===
|
|
1542
|
-
return
|
|
1541
|
+
if (packetType === chunkC6OODRWD_cjs.Packet.PACKET_TYPE) {
|
|
1542
|
+
return chunkC6OODRWD_cjs.Packet.fromBytes(data);
|
|
1543
1543
|
}
|
|
1544
1544
|
return new UnknownPacket(packetType, data);
|
|
1545
1545
|
}
|
|
@@ -1730,7 +1730,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1730
1730
|
const existing = inputsByAssetId.get(asset.assetId);
|
|
1731
1731
|
inputsByAssetId.set(asset.assetId, [
|
|
1732
1732
|
...existing ?? [],
|
|
1733
|
-
|
|
1733
|
+
chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount)
|
|
1734
1734
|
]);
|
|
1735
1735
|
}
|
|
1736
1736
|
}
|
|
@@ -1742,7 +1742,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1742
1742
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
1743
1743
|
outputsByAssetId.set(asset.assetId, [
|
|
1744
1744
|
...existing ?? [],
|
|
1745
|
-
|
|
1745
|
+
chunkC6OODRWD_cjs.AssetOutput.create(outputIndex, asset.amount)
|
|
1746
1746
|
]);
|
|
1747
1747
|
}
|
|
1748
1748
|
}
|
|
@@ -1753,7 +1753,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1753
1753
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
1754
1754
|
outputsByAssetId.set(asset.assetId, [
|
|
1755
1755
|
...existing ?? [],
|
|
1756
|
-
|
|
1756
|
+
chunkC6OODRWD_cjs.AssetOutput.create(outputIndex, asset.amount)
|
|
1757
1757
|
]);
|
|
1758
1758
|
}
|
|
1759
1759
|
}
|
|
@@ -1762,11 +1762,11 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1762
1762
|
for (const assetIdStr of allAssetIds) {
|
|
1763
1763
|
const inputs = inputsByAssetId.get(assetIdStr);
|
|
1764
1764
|
const outputs = outputsByAssetId.get(assetIdStr);
|
|
1765
|
-
const assetId =
|
|
1766
|
-
const group =
|
|
1765
|
+
const assetId = chunkC6OODRWD_cjs.AssetId.fromString(assetIdStr);
|
|
1766
|
+
const group = chunkC6OODRWD_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
|
|
1767
1767
|
groups.push(group);
|
|
1768
1768
|
}
|
|
1769
|
-
return
|
|
1769
|
+
return chunkC6OODRWD_cjs.Packet.create(groups);
|
|
1770
1770
|
}
|
|
1771
1771
|
function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
1772
1772
|
const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
|
|
@@ -1801,6 +1801,45 @@ function selectedCoinsToAssetInputs(selectedCoins) {
|
|
|
1801
1801
|
}
|
|
1802
1802
|
return assetInputs;
|
|
1803
1803
|
}
|
|
1804
|
+
function toXOnlySignerHex(pubkeyHex) {
|
|
1805
|
+
const bytes = base.hex.decode(pubkeyHex);
|
|
1806
|
+
if (bytes.length === 33) return base.hex.encode(bytes.slice(1));
|
|
1807
|
+
if (bytes.length === 32) return base.hex.encode(bytes);
|
|
1808
|
+
throw new Error(`invalid signer pubkey length: expected 32 or 33 bytes, got ${bytes.length}`);
|
|
1809
|
+
}
|
|
1810
|
+
function signerSetFromInfo(info) {
|
|
1811
|
+
const active = toXOnlySignerHex(info.signerPubkey);
|
|
1812
|
+
const deprecated = /* @__PURE__ */ new Map();
|
|
1813
|
+
for (const signer of info.deprecatedSigners) {
|
|
1814
|
+
if (!signer.pubkey) continue;
|
|
1815
|
+
deprecated.set(toXOnlySignerHex(signer.pubkey), signer.cutoffDate);
|
|
1816
|
+
}
|
|
1817
|
+
return { active, deprecated };
|
|
1818
|
+
}
|
|
1819
|
+
function classifyAgainstSignerSet(contractServerPubKeyHex, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
1820
|
+
const signerPubKey = toXOnlySignerHex(contractServerPubKeyHex);
|
|
1821
|
+
if (signerPubKey === signerSet.active) {
|
|
1822
|
+
return { status: "CURRENT", signerPubKey };
|
|
1823
|
+
}
|
|
1824
|
+
if (!signerSet.deprecated.has(signerPubKey)) {
|
|
1825
|
+
return { status: "UNKNOWN_SIGNER", signerPubKey };
|
|
1826
|
+
}
|
|
1827
|
+
const cutoffDate = signerSet.deprecated.get(signerPubKey);
|
|
1828
|
+
if (cutoffDate === 0n) {
|
|
1829
|
+
return { status: "DUE_NOW", signerPubKey };
|
|
1830
|
+
}
|
|
1831
|
+
const secondsUntilCutoff = Number(cutoffDate) - nowSeconds;
|
|
1832
|
+
if (secondsUntilCutoff <= 0) {
|
|
1833
|
+
return { status: "EXPIRED", signerPubKey, cutoffDate, secondsUntilCutoff };
|
|
1834
|
+
}
|
|
1835
|
+
return { status: "MIGRATABLE", signerPubKey, cutoffDate, secondsUntilCutoff };
|
|
1836
|
+
}
|
|
1837
|
+
function classifyContractSigner(contractServerPubKeyHex, info, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
1838
|
+
return classifyAgainstSignerSet(contractServerPubKeyHex, signerSetFromInfo(info), nowSeconds);
|
|
1839
|
+
}
|
|
1840
|
+
function isCooperativelyMigratable(status) {
|
|
1841
|
+
return status === "MIGRATABLE" || status === "DUE_NOW";
|
|
1842
|
+
}
|
|
1804
1843
|
function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
1805
1844
|
const MAX_OP_RETURN = 2;
|
|
1806
1845
|
let countOpReturn = 0;
|
|
@@ -1844,7 +1883,7 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
1844
1883
|
}
|
|
1845
1884
|
}
|
|
1846
1885
|
}
|
|
1847
|
-
const tx = new
|
|
1886
|
+
const tx = new chunkC6OODRWD_cjs.Transaction({
|
|
1848
1887
|
version: 3,
|
|
1849
1888
|
lockTime: Number(lockTime)
|
|
1850
1889
|
});
|
|
@@ -1859,7 +1898,7 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
1859
1898
|
},
|
|
1860
1899
|
tapLeafScript: [input.tapLeafScript]
|
|
1861
1900
|
});
|
|
1862
|
-
|
|
1901
|
+
chunkC6OODRWD_cjs.setArkPsbtField(tx, i, chunkC6OODRWD_cjs.VtxoTaprootTree, input.tapTree);
|
|
1863
1902
|
}
|
|
1864
1903
|
for (const output of outputs) {
|
|
1865
1904
|
tx.addOutput(output);
|
|
@@ -2381,6 +2420,20 @@ function validateRecipients(recipients, dustAmount) {
|
|
|
2381
2420
|
}
|
|
2382
2421
|
|
|
2383
2422
|
// src/wallet/vtxo-manager.ts
|
|
2423
|
+
function selectPendingRecoveryOutpoints(contractsWithVtxos, signerSet, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
2424
|
+
const out = /* @__PURE__ */ new Set();
|
|
2425
|
+
for (const { contract, vtxos } of contractsWithVtxos) {
|
|
2426
|
+
const serverPubKey = contract.params.serverPubKey;
|
|
2427
|
+
if (!serverPubKey) continue;
|
|
2428
|
+
if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status !== "EXPIRED") {
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
for (const v of vtxos) {
|
|
2432
|
+
if (isSpendable(v) && !isRecoverable(v)) out.add(`${v.txid}:${v.vout}`);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return out;
|
|
2436
|
+
}
|
|
2384
2437
|
function isSweepCapable(wallet) {
|
|
2385
2438
|
return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet && "signOnchainBoardingTx" in wallet;
|
|
2386
2439
|
}
|
|
@@ -2418,6 +2471,18 @@ function byExpiryAscending(vtxos) {
|
|
|
2418
2471
|
};
|
|
2419
2472
|
return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
|
|
2420
2473
|
}
|
|
2474
|
+
function capSettlementBatch(sorted, maxAmount) {
|
|
2475
|
+
const batch = [];
|
|
2476
|
+
let total = 0n;
|
|
2477
|
+
for (const vtxo of sorted) {
|
|
2478
|
+
if (batch.length >= MAX_VTXOS_PER_SETTLEMENT) break;
|
|
2479
|
+
const next = total + BigInt(vtxo.value);
|
|
2480
|
+
if (maxAmount >= 0n && next > maxAmount) continue;
|
|
2481
|
+
batch.push(vtxo);
|
|
2482
|
+
total = next;
|
|
2483
|
+
}
|
|
2484
|
+
return batch;
|
|
2485
|
+
}
|
|
2421
2486
|
var DEFAULT_THRESHOLD_SECONDS = 259200;
|
|
2422
2487
|
var DEFAULT_THRESHOLD_MS = DEFAULT_THRESHOLD_SECONDS * 1e3;
|
|
2423
2488
|
var DEFAULT_RENEWAL_CONFIG = {
|
|
@@ -2427,7 +2492,8 @@ var DEFAULT_RENEWAL_CONFIG = {
|
|
|
2427
2492
|
var DEFAULT_SETTLEMENT_CONFIG = {
|
|
2428
2493
|
vtxoThreshold: DEFAULT_THRESHOLD_SECONDS,
|
|
2429
2494
|
boardingUtxoSweep: true,
|
|
2430
|
-
pollIntervalMs: 6e4
|
|
2495
|
+
pollIntervalMs: 6e4,
|
|
2496
|
+
deprecatedSignerMigration: true
|
|
2431
2497
|
};
|
|
2432
2498
|
function getRecoverableVtxos(vtxos, dustAmount) {
|
|
2433
2499
|
return vtxos.filter((vtxo) => {
|
|
@@ -2481,6 +2547,51 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
|
|
|
2481
2547
|
(vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || isRecoverable(vtxo) || isSpendable(vtxo) && isExpired(vtxo) || isSubdust(vtxo, dustAmount)
|
|
2482
2548
|
);
|
|
2483
2549
|
}
|
|
2550
|
+
function isMigrationCapable(wallet) {
|
|
2551
|
+
return "arkProvider" in wallet && "arkServerPublicKey" in wallet && "onchainProvider" in wallet && typeof wallet.rotateServerSigner === "function" && typeof wallet.sendSelectedVtxosToSelf === "function" && typeof wallet.getBoardingUtxosForSigners === "function";
|
|
2552
|
+
}
|
|
2553
|
+
function classifiedToRef(c) {
|
|
2554
|
+
return {
|
|
2555
|
+
txid: c.vtxo.txid,
|
|
2556
|
+
vout: c.vtxo.vout,
|
|
2557
|
+
value: c.vtxo.value,
|
|
2558
|
+
signerPubKey: c.classification.signerPubKey,
|
|
2559
|
+
cutoffDate: c.classification.cutoffDate
|
|
2560
|
+
};
|
|
2561
|
+
}
|
|
2562
|
+
function classifiedBoardingToRef(c) {
|
|
2563
|
+
return {
|
|
2564
|
+
txid: c.coin.txid,
|
|
2565
|
+
vout: c.coin.vout,
|
|
2566
|
+
value: c.coin.value,
|
|
2567
|
+
signerPubKey: c.classification.signerPubKey,
|
|
2568
|
+
cutoffDate: c.classification.cutoffDate
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
function mergeSignerReports(...reportLists) {
|
|
2572
|
+
const bySigner = /* @__PURE__ */ new Map();
|
|
2573
|
+
for (const list of reportLists) {
|
|
2574
|
+
for (const r of list) {
|
|
2575
|
+
const existing = bySigner.get(r.signerPubKey);
|
|
2576
|
+
if (existing) {
|
|
2577
|
+
existing.vtxoCount += r.vtxoCount;
|
|
2578
|
+
existing.totalValue += r.totalValue;
|
|
2579
|
+
existing.boardingCount += r.boardingCount;
|
|
2580
|
+
existing.boardingValue += r.boardingValue;
|
|
2581
|
+
existing.recoverableCount += r.recoverableCount;
|
|
2582
|
+
existing.recoverableValue += r.recoverableValue;
|
|
2583
|
+
existing.awaitingSweepCount += r.awaitingSweepCount;
|
|
2584
|
+
existing.awaitingSweepValue += r.awaitingSweepValue;
|
|
2585
|
+
if (r.nextSweepEta !== void 0) {
|
|
2586
|
+
existing.nextSweepEta = existing.nextSweepEta === void 0 ? r.nextSweepEta : Math.min(existing.nextSweepEta, r.nextSweepEta);
|
|
2587
|
+
}
|
|
2588
|
+
} else {
|
|
2589
|
+
bySigner.set(r.signerPubKey, { ...r });
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
return Array.from(bySigner.values());
|
|
2594
|
+
}
|
|
2484
2595
|
var VtxoManager = class _VtxoManager {
|
|
2485
2596
|
constructor(wallet, renewalConfig, settlementConfig) {
|
|
2486
2597
|
this.wallet = wallet;
|
|
@@ -2496,15 +2607,9 @@ var VtxoManager = class _VtxoManager {
|
|
|
2496
2607
|
} else {
|
|
2497
2608
|
this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
|
|
2498
2609
|
}
|
|
2499
|
-
this.contractEventsSubscriptionReady = this.initializeSubscription()
|
|
2500
|
-
(subscription) => {
|
|
2501
|
-
this.contractEventsSubscription = subscription;
|
|
2502
|
-
return subscription;
|
|
2503
|
-
}
|
|
2504
|
-
);
|
|
2610
|
+
this.contractEventsSubscriptionReady = this.initializeSubscription();
|
|
2505
2611
|
}
|
|
2506
2612
|
settlementConfig;
|
|
2507
|
-
contractEventsSubscription;
|
|
2508
2613
|
contractEventsSubscriptionReady;
|
|
2509
2614
|
disposePromise;
|
|
2510
2615
|
pollTimeoutId;
|
|
@@ -2541,6 +2646,15 @@ var VtxoManager = class _VtxoManager {
|
|
|
2541
2646
|
lastVtxoSpentRefreshTimestamp = 0;
|
|
2542
2647
|
vtxoSpentRefreshPromise;
|
|
2543
2648
|
static VTXO_SPENT_REFRESH_COOLDOWN_MS = 3e4;
|
|
2649
|
+
// Cooldown/backoff for the automatic deprecated-signer migration pass.
|
|
2650
|
+
// Mirrors the periodic-settle machinery so a server-side migration failure
|
|
2651
|
+
// (e.g. arkd not yet accepting old-key inputs, or a closed cutoff window)
|
|
2652
|
+
// backs off exponentially instead of re-submitting an identical intent on
|
|
2653
|
+
// every poll. The manual migrateDeprecatedSignerVtxos() bypasses this.
|
|
2654
|
+
lastMigrationTimestamp = 0;
|
|
2655
|
+
consecutiveMigrationFailures = 0;
|
|
2656
|
+
static MIGRATION_COOLDOWN_MS = 3e4;
|
|
2657
|
+
static MIGRATION_MAX_BACKOFF_MS = 5 * 60 * 1e3;
|
|
2544
2658
|
// ========== Recovery Methods ==========
|
|
2545
2659
|
/**
|
|
2546
2660
|
* Recover swept/expired virtual outputs by settling them back to the wallet's Arkade address.
|
|
@@ -2581,16 +2695,21 @@ var VtxoManager = class _VtxoManager {
|
|
|
2581
2695
|
if (vtxosToRecover.length === 0) {
|
|
2582
2696
|
throw new Error("No recoverable VTXOs found");
|
|
2583
2697
|
}
|
|
2584
|
-
|
|
2698
|
+
const info = await this.getInfoProvider()?.getInfo();
|
|
2699
|
+
const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
|
|
2700
|
+
const capped = capSettlementBatch(byValueDescending(vtxosToRecover), vtxoMaxAmount);
|
|
2701
|
+
if (capped.length < vtxosToRecover.length) {
|
|
2585
2702
|
const recoverableCount = vtxosToRecover.length;
|
|
2586
|
-
const capped = byValueDescending(vtxosToRecover).slice(0, MAX_VTXOS_PER_SETTLEMENT);
|
|
2587
2703
|
({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
|
|
2588
2704
|
if (vtxosToRecover.length === 0) {
|
|
2589
2705
|
throw new Error(
|
|
2590
|
-
`Capped recovery batch (highest-value
|
|
2706
|
+
`Capped recovery batch (highest-value subset of ${recoverableCount} recoverable VTXOs within the ${MAX_VTXOS_PER_SETTLEMENT}-input and ${vtxoMaxAmount}-sat limits) is below the dust threshold ${dustAmount}`
|
|
2591
2707
|
);
|
|
2592
2708
|
}
|
|
2593
2709
|
}
|
|
2710
|
+
if (info && isMigrationCapable(this.wallet)) {
|
|
2711
|
+
await this.rotateForRecoverableInputs(vtxosToRecover, info);
|
|
2712
|
+
}
|
|
2594
2713
|
const arkAddress = await this.wallet.getAddress();
|
|
2595
2714
|
return this.wallet.settle(
|
|
2596
2715
|
{
|
|
@@ -2740,8 +2859,24 @@ var VtxoManager = class _VtxoManager {
|
|
|
2740
2859
|
if (vtxos.length === 0) {
|
|
2741
2860
|
throw new Error("No VTXOs available to renew");
|
|
2742
2861
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2862
|
+
const info = await this.getInfoProvider()?.getInfo();
|
|
2863
|
+
const vtxoMaxAmount = info?.vtxoMaxAmount ?? -1n;
|
|
2864
|
+
const capped = capSettlementBatch(byExpiryAscending(vtxos), vtxoMaxAmount);
|
|
2865
|
+
if (vtxoMaxAmount >= 0n) {
|
|
2866
|
+
const oversized = vtxos.filter((vtxo) => BigInt(vtxo.value) > vtxoMaxAmount);
|
|
2867
|
+
if (oversized.length > 0) {
|
|
2868
|
+
console.warn(
|
|
2869
|
+
`Renewal: ${oversized.length} VTXO(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be renewed; they risk unilateral exit`
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
if (capped.length < vtxos.length) {
|
|
2874
|
+
vtxos = capped;
|
|
2875
|
+
if (vtxos.length === 0) {
|
|
2876
|
+
throw new Error(
|
|
2877
|
+
`No VTXOs available to renew within the per-output limit ${vtxoMaxAmount}`
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2745
2880
|
}
|
|
2746
2881
|
const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
2747
2882
|
const dustAmount = getDustAmount(this.wallet);
|
|
@@ -2750,6 +2885,9 @@ var VtxoManager = class _VtxoManager {
|
|
|
2750
2885
|
`Total amount ${totalAmount} is below dust threshold ${dustAmount}`
|
|
2751
2886
|
);
|
|
2752
2887
|
}
|
|
2888
|
+
if (info && isMigrationCapable(this.wallet)) {
|
|
2889
|
+
await this.rotateForRecoverableInputs(vtxos, info);
|
|
2890
|
+
}
|
|
2753
2891
|
const arkAddress = await this.wallet.getAddress();
|
|
2754
2892
|
const txid = await this.wallet.settle(
|
|
2755
2893
|
{
|
|
@@ -2868,7 +3006,7 @@ var VtxoManager = class _VtxoManager {
|
|
|
2868
3006
|
`Sweep not economical: output ${outputAmount} sats after ${fee} sats fee is below dust (${dustAmount} sats)`
|
|
2869
3007
|
);
|
|
2870
3008
|
}
|
|
2871
|
-
const tx = new
|
|
3009
|
+
const tx = new chunkC6OODRWD_cjs.Transaction();
|
|
2872
3010
|
for (const utxo of expiredUtxos) {
|
|
2873
3011
|
const utxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(utxo.tapTree);
|
|
2874
3012
|
const utxoExitLeaf = utxoScript.leaves.find(
|
|
@@ -2900,6 +3038,472 @@ var VtxoManager = class _VtxoManager {
|
|
|
2900
3038
|
this.knownBoardingUtxos.add(`${txid}:0`);
|
|
2901
3039
|
return txid;
|
|
2902
3040
|
}
|
|
3041
|
+
// ========== Deprecated-Signer Migration Methods ==========
|
|
3042
|
+
/**
|
|
3043
|
+
* Cooperatively migrate VTXOs minted under a now-deprecated server signer
|
|
3044
|
+
* to the wallet's active-signer address. See {@link IVtxoManager}.
|
|
3045
|
+
*/
|
|
3046
|
+
async migrateDeprecatedSignerVtxos(options) {
|
|
3047
|
+
return this.migrateCore(options);
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Machine-readable status of every deprecated server signer the wallet
|
|
3051
|
+
* currently holds funds under (Section 6), without migrating. Covers both
|
|
3052
|
+
* VTXO and boarding holdings (Section 7), merged per signer.
|
|
3053
|
+
*
|
|
3054
|
+
* @remarks This is no longer a pure repository/info read: surfacing boarding
|
|
3055
|
+
* holdings fans out per boarding address (`getCoins` round trips) and
|
|
3056
|
+
* refreshes the UTXO cache via `saveUtxos`.
|
|
3057
|
+
*/
|
|
3058
|
+
async getDeprecatedSignerStatus() {
|
|
3059
|
+
const wallet = this.requireMigrationCapableWallet();
|
|
3060
|
+
const info = await wallet.arkProvider.getInfo();
|
|
3061
|
+
const { reports: vtxoReports } = await this.classifyDeprecatedSignerContracts(info);
|
|
3062
|
+
const { reports: boardingReports } = await this.classifyDeprecatedSignerBoarding(info);
|
|
3063
|
+
return mergeSignerReports(vtxoReports, boardingReports);
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Core migration routine shared by the manual API and the automatic poll
|
|
3067
|
+
* pass. Fetches a fresh {@link ArkInfo}, applies a mid-session signer
|
|
3068
|
+
* rotation when the wallet's own snapshot signer has been deprecated,
|
|
3069
|
+
* selects spendable VTXOs under deprecated-signer contracts (cutoff-first),
|
|
3070
|
+
* and settles them to the active-signer Ark address.
|
|
3071
|
+
*/
|
|
3072
|
+
async migrateCore(options) {
|
|
3073
|
+
const wallet = this.requireMigrationCapableWallet();
|
|
3074
|
+
const info = await wallet.arkProvider.getInfo();
|
|
3075
|
+
const signerSet = signerSetFromInfo(info);
|
|
3076
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
3077
|
+
const walletSignerHex = base.hex.encode(wallet.arkServerPublicKey);
|
|
3078
|
+
const walletClass = classifyAgainstSignerSet(walletSignerHex, signerSet, nowSeconds);
|
|
3079
|
+
if (signerSet.deprecated.size === 0 && walletClass.status === "CURRENT") {
|
|
3080
|
+
return { rotated: false, expired: [], signers: [] };
|
|
3081
|
+
}
|
|
3082
|
+
if (walletClass.status === "UNKNOWN_SIGNER") {
|
|
3083
|
+
const { reports: vtxoReports2 } = await this.classifyDeprecatedSignerContracts(info);
|
|
3084
|
+
const { reports: boardingReports2 } = await this.classifyDeprecatedSignerBoarding(info);
|
|
3085
|
+
return {
|
|
3086
|
+
rotated: false,
|
|
3087
|
+
expired: [],
|
|
3088
|
+
signers: mergeSignerReports(vtxoReports2, boardingReports2),
|
|
3089
|
+
skipped: "unknown-wallet-signer"
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
const rotated = await this.ensureReceiveOnActiveSigner(info);
|
|
3093
|
+
const {
|
|
3094
|
+
reports: vtxoReports,
|
|
3095
|
+
migratable: vtxoMigratable,
|
|
3096
|
+
expired: vtxoExpired
|
|
3097
|
+
} = await this.classifyDeprecatedSignerContracts(info);
|
|
3098
|
+
const {
|
|
3099
|
+
reports: boardingReports,
|
|
3100
|
+
migratable: boardingMigratable,
|
|
3101
|
+
expired: boardingExpired
|
|
3102
|
+
} = await this.classifyDeprecatedSignerBoarding(info);
|
|
3103
|
+
const reports = mergeSignerReports(vtxoReports, boardingReports);
|
|
3104
|
+
const expiredRefs = [
|
|
3105
|
+
...vtxoExpired.map(classifiedToRef),
|
|
3106
|
+
...boardingExpired.map(classifiedBoardingToRef)
|
|
3107
|
+
];
|
|
3108
|
+
if (vtxoMigratable.length === 0 && boardingMigratable.length === 0) {
|
|
3109
|
+
return {
|
|
3110
|
+
rotated,
|
|
3111
|
+
expired: expiredRefs,
|
|
3112
|
+
signers: reports,
|
|
3113
|
+
skipped: "no-deprecated-vtxos"
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
const vtxoMaxAmount = info.vtxoMaxAmount;
|
|
3117
|
+
const dustAmount = getDustAmount(this.wallet);
|
|
3118
|
+
const report = {
|
|
3119
|
+
rotated,
|
|
3120
|
+
expired: expiredRefs,
|
|
3121
|
+
signers: reports
|
|
3122
|
+
};
|
|
3123
|
+
if (vtxoMigratable.length > 0) {
|
|
3124
|
+
report.vtxos = await this.runMigrationLeg(
|
|
3125
|
+
vtxoMigratable,
|
|
3126
|
+
(c) => c.vtxo.value,
|
|
3127
|
+
classifiedToRef,
|
|
3128
|
+
vtxoMaxAmount,
|
|
3129
|
+
dustAmount,
|
|
3130
|
+
"VTXO",
|
|
3131
|
+
(capped) => wallet.sendSelectedVtxosToSelf(capped.map((c) => c.vtxo))
|
|
3132
|
+
);
|
|
3133
|
+
}
|
|
3134
|
+
if (boardingMigratable.length > 0) {
|
|
3135
|
+
report.boarding = await this.runMigrationLeg(
|
|
3136
|
+
boardingMigratable,
|
|
3137
|
+
(c) => c.coin.value,
|
|
3138
|
+
classifiedBoardingToRef,
|
|
3139
|
+
vtxoMaxAmount,
|
|
3140
|
+
dustAmount,
|
|
3141
|
+
"boarding",
|
|
3142
|
+
async (capped) => {
|
|
3143
|
+
const arkAddress = await this.wallet.getAddress();
|
|
3144
|
+
const totalAmount = capped.reduce((sum, c) => sum + BigInt(c.coin.value), 0n);
|
|
3145
|
+
return this.wallet.settle(
|
|
3146
|
+
{
|
|
3147
|
+
inputs: capped.map((c) => c.coin),
|
|
3148
|
+
outputs: [{ address: arkAddress, amount: totalAmount }]
|
|
3149
|
+
},
|
|
3150
|
+
options?.eventCallback
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
return report;
|
|
3156
|
+
}
|
|
3157
|
+
/**
|
|
3158
|
+
* Size and submit one migration leg. Filters inputs whose value alone
|
|
3159
|
+
* exceeds the per-output ceiling (`vtxoMaxAmount`; `< 0` means no limit) —
|
|
3160
|
+
* those can never form a ≤-ceiling output and must exit unilaterally — then
|
|
3161
|
+
* caps the rest (highest-value first; bounded by {@link MAX_VTXOS_PER_SETTLEMENT}
|
|
3162
|
+
* AND a gross total within `vtxoMaxAmount`), applies the protocol dust floor,
|
|
3163
|
+
* and submits the capped batch through `submit`. A throw from `submit` lands
|
|
3164
|
+
* in `error`; the caller's other leg still runs.
|
|
3165
|
+
*
|
|
3166
|
+
* Migration is mandatory and fee-exempt: every selected input moves at its
|
|
3167
|
+
* full value, so the gross total IS the aggregated output amount (kept under
|
|
3168
|
+
* the server ceiling by the cap). The dust floor guards the degenerate cases
|
|
3169
|
+
* where every input was oversized or the whole holding sums below dust.
|
|
3170
|
+
*/
|
|
3171
|
+
async runMigrationLeg(candidates, valueOf, toRef, vtxoMaxAmount, dustAmount, legName, submit) {
|
|
3172
|
+
const oversizedRefs = [];
|
|
3173
|
+
const sized = [];
|
|
3174
|
+
for (const c of candidates) {
|
|
3175
|
+
if (vtxoMaxAmount >= 0n && BigInt(valueOf(c)) > vtxoMaxAmount) {
|
|
3176
|
+
oversizedRefs.push(toRef(c));
|
|
3177
|
+
} else {
|
|
3178
|
+
sized.push(c);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
if (oversizedRefs.length > 0) {
|
|
3182
|
+
console.warn(
|
|
3183
|
+
`Deprecated-signer migration (${legName}): ${oversizedRefs.length} input(s) exceed the per-output limit ${vtxoMaxAmount} and cannot be migrated cooperatively; they require a unilateral exit.`
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
const oversizedField = oversizedRefs.length > 0 ? { oversized: oversizedRefs } : {};
|
|
3187
|
+
const capped = capSettlementBatch(
|
|
3188
|
+
byValueDescending(sized.map((c) => ({ value: valueOf(c), c }))),
|
|
3189
|
+
vtxoMaxAmount
|
|
3190
|
+
).map((w) => w.c);
|
|
3191
|
+
const deferred = sized.length - capped.length;
|
|
3192
|
+
const totalAmount = capped.reduce((sum, c) => sum + BigInt(valueOf(c)), 0n);
|
|
3193
|
+
if (totalAmount < dustAmount) {
|
|
3194
|
+
const onlyOversized = sized.length === 0 && oversizedRefs.length > 0;
|
|
3195
|
+
return {
|
|
3196
|
+
migrated: [],
|
|
3197
|
+
skipped: onlyOversized ? "oversized-only" : "below-dust",
|
|
3198
|
+
...oversizedField
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
try {
|
|
3202
|
+
const txid = await submit(capped);
|
|
3203
|
+
return {
|
|
3204
|
+
txid,
|
|
3205
|
+
migrated: capped.map(toRef),
|
|
3206
|
+
...deferred > 0 ? { deferred } : {},
|
|
3207
|
+
...oversizedField
|
|
3208
|
+
};
|
|
3209
|
+
} catch (e) {
|
|
3210
|
+
return {
|
|
3211
|
+
migrated: [],
|
|
3212
|
+
error: e instanceof Error ? e.message : String(e),
|
|
3213
|
+
...oversizedField
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
/**
|
|
3218
|
+
* Enumerate the wallet's `default`/`delegate` contracts, classify each
|
|
3219
|
+
* against the fresh signer set, and split their spendable VTXOs into
|
|
3220
|
+
* cooperatively-migratable and cutoff-expired sets while building the
|
|
3221
|
+
* per-signer status report. Current-signer contracts are skipped; swept
|
|
3222
|
+
* (recoverable) VTXOs are excluded from the settle sets — those follow the
|
|
3223
|
+
* recovery path — but are still counted on EXPIRED report rows
|
|
3224
|
+
* (`recoverableCount`) so post-cutoff funds in flight stay visible.
|
|
3225
|
+
*/
|
|
3226
|
+
async classifyDeprecatedSignerContracts(info) {
|
|
3227
|
+
const cm = await this.wallet.getContractManager();
|
|
3228
|
+
const signerSet = signerSetFromInfo(info);
|
|
3229
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
3230
|
+
const contractsWithVtxos = await cm.getContractsWithVtxos({
|
|
3231
|
+
type: ["default", "delegate"]
|
|
3232
|
+
});
|
|
3233
|
+
const reportsBySigner = /* @__PURE__ */ new Map();
|
|
3234
|
+
const migratable = [];
|
|
3235
|
+
const expired = [];
|
|
3236
|
+
for (const { contract, vtxos } of contractsWithVtxos) {
|
|
3237
|
+
const serverPubKey = contract.params.serverPubKey;
|
|
3238
|
+
if (!serverPubKey) continue;
|
|
3239
|
+
const cls = classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds);
|
|
3240
|
+
if (cls.status === "CURRENT") continue;
|
|
3241
|
+
const recoverable = vtxos.filter((v) => isRecoverable(v));
|
|
3242
|
+
const spendable = vtxos.filter((v) => isSpendable(v) && !isRecoverable(v));
|
|
3243
|
+
const value = spendable.reduce((sum, v) => sum + v.value, 0);
|
|
3244
|
+
let recoverableCount = 0;
|
|
3245
|
+
let recoverableValue = 0;
|
|
3246
|
+
let awaitingSweepCount = 0;
|
|
3247
|
+
let awaitingSweepValue = 0;
|
|
3248
|
+
let nextSweepEta;
|
|
3249
|
+
if (cls.status === "EXPIRED") {
|
|
3250
|
+
recoverableCount = recoverable.length;
|
|
3251
|
+
recoverableValue = recoverable.reduce((sum, v) => sum + v.value, 0);
|
|
3252
|
+
awaitingSweepCount = spendable.length;
|
|
3253
|
+
awaitingSweepValue = value;
|
|
3254
|
+
for (const v of spendable) {
|
|
3255
|
+
const exp = v.virtualStatus.batchExpiry;
|
|
3256
|
+
if (exp !== void 0 && (nextSweepEta === void 0 || exp < nextSweepEta)) {
|
|
3257
|
+
nextSweepEta = exp;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const existing = reportsBySigner.get(cls.signerPubKey);
|
|
3262
|
+
if (existing) {
|
|
3263
|
+
existing.vtxoCount += spendable.length;
|
|
3264
|
+
existing.totalValue += value;
|
|
3265
|
+
existing.recoverableCount += recoverableCount;
|
|
3266
|
+
existing.recoverableValue += recoverableValue;
|
|
3267
|
+
existing.awaitingSweepCount += awaitingSweepCount;
|
|
3268
|
+
existing.awaitingSweepValue += awaitingSweepValue;
|
|
3269
|
+
if (nextSweepEta !== void 0) {
|
|
3270
|
+
existing.nextSweepEta = existing.nextSweepEta === void 0 ? nextSweepEta : Math.min(existing.nextSweepEta, nextSweepEta);
|
|
3271
|
+
}
|
|
3272
|
+
} else {
|
|
3273
|
+
reportsBySigner.set(cls.signerPubKey, {
|
|
3274
|
+
signerPubKey: cls.signerPubKey,
|
|
3275
|
+
status: cls.status,
|
|
3276
|
+
cutoffDate: cls.cutoffDate,
|
|
3277
|
+
secondsUntilCutoff: cls.secondsUntilCutoff,
|
|
3278
|
+
vtxoCount: spendable.length,
|
|
3279
|
+
totalValue: value,
|
|
3280
|
+
boardingCount: 0,
|
|
3281
|
+
boardingValue: 0,
|
|
3282
|
+
recoverableCount,
|
|
3283
|
+
recoverableValue,
|
|
3284
|
+
awaitingSweepCount,
|
|
3285
|
+
awaitingSweepValue,
|
|
3286
|
+
nextSweepEta
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
if (isCooperativelyMigratable(cls.status)) {
|
|
3290
|
+
for (const v of spendable) {
|
|
3291
|
+
if (!v.virtualStatus.batchExpiry) continue;
|
|
3292
|
+
migratable.push({ vtxo: v, classification: cls });
|
|
3293
|
+
}
|
|
3294
|
+
} else if (cls.status === "EXPIRED") {
|
|
3295
|
+
for (const v of spendable) expired.push({ vtxo: v, classification: cls });
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return {
|
|
3299
|
+
reports: Array.from(reportsBySigner.values()),
|
|
3300
|
+
migratable,
|
|
3301
|
+
expired
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Boarding sibling of {@link classifyDeprecatedSignerContracts} (Section 7):
|
|
3306
|
+
* fan out over the wallet's boarding addresses (current + historical), group
|
|
3307
|
+
* the on-chain UTXOs per address, classify each address's signer against the
|
|
3308
|
+
* fresh signer set, and split the confirmed boarding coins into cooperatively-
|
|
3309
|
+
* migratable and cutoff-expired sets while building the per-signer report.
|
|
3310
|
+
*
|
|
3311
|
+
* Discovery sees the active signer plus EVERY deprecated key (EXPIRED
|
|
3312
|
+
* included), so expired-signer boarding is still reported; migration
|
|
3313
|
+
* eligibility is gated afterwards by {@link isCooperativelyMigratable} and a
|
|
3314
|
+
* per-row boarding-output CSV check — never by the fetch. Current-signer
|
|
3315
|
+
* coins are classified `CURRENT` and ignored; foreign-ASP rows are excluded
|
|
3316
|
+
* because their keys are not in the signer set.
|
|
3317
|
+
*/
|
|
3318
|
+
async classifyDeprecatedSignerBoarding(info) {
|
|
3319
|
+
const wallet = this.requireMigrationCapableWallet();
|
|
3320
|
+
const signerSet = signerSetFromInfo(info);
|
|
3321
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
3322
|
+
const allowed = /* @__PURE__ */ new Set([signerSet.active, ...signerSet.deprecated.keys()]);
|
|
3323
|
+
const groups = await wallet.getBoardingUtxosForSigners(allowed);
|
|
3324
|
+
let chainTipHeight;
|
|
3325
|
+
if (groups.some((g) => g.csvTimelock.type === "blocks")) {
|
|
3326
|
+
const tip = await wallet.onchainProvider.getChainTip();
|
|
3327
|
+
chainTipHeight = tip.height;
|
|
3328
|
+
}
|
|
3329
|
+
const reportsBySigner = /* @__PURE__ */ new Map();
|
|
3330
|
+
const migratable = [];
|
|
3331
|
+
const expired = [];
|
|
3332
|
+
for (const group of groups) {
|
|
3333
|
+
const cls = classifyAgainstSignerSet(group.serverPubKey, signerSet, nowSeconds);
|
|
3334
|
+
if (cls.status === "CURRENT") continue;
|
|
3335
|
+
const confirmed = group.coins.filter((c) => c.status.confirmed);
|
|
3336
|
+
if (confirmed.length === 0) continue;
|
|
3337
|
+
const value = confirmed.reduce((sum, c) => sum + c.value, 0);
|
|
3338
|
+
const existing = reportsBySigner.get(cls.signerPubKey);
|
|
3339
|
+
if (existing) {
|
|
3340
|
+
existing.boardingCount += confirmed.length;
|
|
3341
|
+
existing.boardingValue += value;
|
|
3342
|
+
} else {
|
|
3343
|
+
reportsBySigner.set(cls.signerPubKey, {
|
|
3344
|
+
signerPubKey: cls.signerPubKey,
|
|
3345
|
+
status: cls.status,
|
|
3346
|
+
cutoffDate: cls.cutoffDate,
|
|
3347
|
+
secondsUntilCutoff: cls.secondsUntilCutoff,
|
|
3348
|
+
vtxoCount: 0,
|
|
3349
|
+
totalValue: 0,
|
|
3350
|
+
boardingCount: confirmed.length,
|
|
3351
|
+
boardingValue: value,
|
|
3352
|
+
// Boarding UTXOs don't carry an offchain sweep lifecycle; the
|
|
3353
|
+
// post-cutoff recover-on-sweep fields apply to VTXOs only and
|
|
3354
|
+
// are merged in from the VTXO classifier (mergeSignerReports).
|
|
3355
|
+
recoverableCount: 0,
|
|
3356
|
+
recoverableValue: 0,
|
|
3357
|
+
awaitingSweepCount: 0,
|
|
3358
|
+
awaitingSweepValue: 0
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
for (const coin of confirmed) {
|
|
3362
|
+
const boardingExpired = hasBoardingTxExpired(
|
|
3363
|
+
coin,
|
|
3364
|
+
group.csvTimelock,
|
|
3365
|
+
chainTipHeight
|
|
3366
|
+
);
|
|
3367
|
+
if (isCooperativelyMigratable(cls.status) && !boardingExpired) {
|
|
3368
|
+
migratable.push({ coin, classification: cls });
|
|
3369
|
+
} else if (cls.status === "EXPIRED") {
|
|
3370
|
+
expired.push({ coin, classification: cls });
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
return {
|
|
3375
|
+
reports: Array.from(reportsBySigner.values()),
|
|
3376
|
+
migratable,
|
|
3377
|
+
expired
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Automatic migration pass invoked from the poll loop. Self-contained:
|
|
3382
|
+
* respects an exponential cooldown and logs failures rather than throwing,
|
|
3383
|
+
* so a persistently failing migration backs off instead of re-submitting
|
|
3384
|
+
* an identical intent every cycle.
|
|
3385
|
+
*/
|
|
3386
|
+
async runMigrationPass() {
|
|
3387
|
+
const cooldownMs = Math.min(
|
|
3388
|
+
_VtxoManager.MIGRATION_COOLDOWN_MS * Math.pow(2, this.consecutiveMigrationFailures),
|
|
3389
|
+
_VtxoManager.MIGRATION_MAX_BACKOFF_MS
|
|
3390
|
+
);
|
|
3391
|
+
if (Date.now() - this.lastMigrationTimestamp < cooldownMs) return;
|
|
3392
|
+
try {
|
|
3393
|
+
const report = await this.migrateCore();
|
|
3394
|
+
const legError = report.vtxos?.error ?? report.boarding?.error;
|
|
3395
|
+
if (legError) {
|
|
3396
|
+
this.consecutiveMigrationFailures++;
|
|
3397
|
+
console.error("Deprecated-signer migration leg failed:", legError);
|
|
3398
|
+
} else {
|
|
3399
|
+
this.consecutiveMigrationFailures = 0;
|
|
3400
|
+
}
|
|
3401
|
+
} catch (e) {
|
|
3402
|
+
this.consecutiveMigrationFailures++;
|
|
3403
|
+
console.error("Error during deprecated-signer migration:", e);
|
|
3404
|
+
} finally {
|
|
3405
|
+
this.lastMigrationTimestamp = Date.now();
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
/** Asserts migration capability and returns the typed wallet. */
|
|
3409
|
+
requireMigrationCapableWallet() {
|
|
3410
|
+
if (!isMigrationCapable(this.wallet)) {
|
|
3411
|
+
throw new Error(
|
|
3412
|
+
"Deprecated-signer migration requires a Wallet instance with arkProvider, arkServerPublicKey, and rotateServerSigner"
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
return this.wallet;
|
|
3416
|
+
}
|
|
3417
|
+
/**
|
|
3418
|
+
* If the wallet's own construction-time signer snapshot has been deprecated,
|
|
3419
|
+
* re-derive its receive/boarding state under the active signer so any output
|
|
3420
|
+
* built afterwards commits to the active key. No-op when the snapshot is
|
|
3421
|
+
* already current. Returns whether a rotation was applied. Treats an
|
|
3422
|
+
* unknown-signer snapshot as "do not rotate" (caller decides).
|
|
3423
|
+
*
|
|
3424
|
+
* Shared by the migration pass (where the wallet's own snapshot is the thing
|
|
3425
|
+
* being migrated) and the recovery/renewal/periodic-settle paths (via
|
|
3426
|
+
* {@link rotateForRecoverableInputs}), so a swept old-signer VTXO recovered
|
|
3427
|
+
* after cutoff re-mints under the active signer rather than re-committing to
|
|
3428
|
+
* the deprecated key (Section 6 / post-cutoff). `rotateServerSigner` is
|
|
3429
|
+
* idempotent and serializes itself against HD receive rotation, so repeated
|
|
3430
|
+
* calls across passes are safe.
|
|
3431
|
+
*/
|
|
3432
|
+
async ensureReceiveOnActiveSigner(info) {
|
|
3433
|
+
const wallet = this.requireMigrationCapableWallet();
|
|
3434
|
+
const signerSet = signerSetFromInfo(info);
|
|
3435
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
3436
|
+
const walletClass = classifyAgainstSignerSet(
|
|
3437
|
+
base.hex.encode(wallet.arkServerPublicKey),
|
|
3438
|
+
signerSet,
|
|
3439
|
+
nowSeconds
|
|
3440
|
+
);
|
|
3441
|
+
if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
|
|
3442
|
+
return false;
|
|
3443
|
+
}
|
|
3444
|
+
await wallet.rotateServerSigner(base.hex.decode(info.signerPubkey), info.checkpointTapscript);
|
|
3445
|
+
return true;
|
|
3446
|
+
}
|
|
3447
|
+
/**
|
|
3448
|
+
* Rotation guard for the recovery-bearing settle paths (recover / renew /
|
|
3449
|
+
* periodic settle). Pins the wallet's receive snapshot to the active signer
|
|
3450
|
+
* before they build their output, but ONLY when this pass actually carries
|
|
3451
|
+
* an input minted under a deprecated signer — so a routine current-signer
|
|
3452
|
+
* settle on a long-lived pre-rotation instance does not eagerly rotate.
|
|
3453
|
+
*
|
|
3454
|
+
* Cheap in the common case: a watch-only/proxy wallet (not migration-capable)
|
|
3455
|
+
* and a current/unknown wallet snapshot both short-circuit before the
|
|
3456
|
+
* contract round-trip, so the only instance that pays for the input scan is
|
|
3457
|
+
* the long-lived deprecated-snapshot one that genuinely needs rotating.
|
|
3458
|
+
*
|
|
3459
|
+
* Runs OUTSIDE any `renewalInProgress` window the caller sets, and
|
|
3460
|
+
* `rotateServerSigner` does not depend on that flag, so it cannot deadlock
|
|
3461
|
+
* against the receive rotator. Returns whether a rotation was applied.
|
|
3462
|
+
*/
|
|
3463
|
+
async rotateForRecoverableInputs(inputs, info) {
|
|
3464
|
+
if (!isMigrationCapable(this.wallet)) return false;
|
|
3465
|
+
const signerSet = signerSetFromInfo(info);
|
|
3466
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
3467
|
+
const walletClass = classifyAgainstSignerSet(
|
|
3468
|
+
base.hex.encode(this.wallet.arkServerPublicKey),
|
|
3469
|
+
signerSet,
|
|
3470
|
+
nowSeconds
|
|
3471
|
+
);
|
|
3472
|
+
if (walletClass.status === "CURRENT" || walletClass.status === "UNKNOWN_SIGNER") {
|
|
3473
|
+
return false;
|
|
3474
|
+
}
|
|
3475
|
+
if (!await this.anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds)) {
|
|
3476
|
+
return false;
|
|
3477
|
+
}
|
|
3478
|
+
return this.ensureReceiveOnActiveSigner(info);
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Whether any of the given input outpoints belongs to a contract whose
|
|
3482
|
+
* server signer classifies as non-`CURRENT` against the fresh signer set —
|
|
3483
|
+
* i.e. a deprecated-signer (incl. EXPIRED) input. Maps outpoints to their
|
|
3484
|
+
* owning contract via the ContractManager so it works on the typed
|
|
3485
|
+
* {@link ExtendedVirtualCoin}/{@link ExtendedCoin} inputs the recovery paths
|
|
3486
|
+
* carry (which don't expose `contractScript`).
|
|
3487
|
+
*/
|
|
3488
|
+
async anyInputUnderDeprecatedSigner(inputs, signerSet, nowSeconds) {
|
|
3489
|
+
if (inputs.length === 0) return false;
|
|
3490
|
+
const wanted = new Set(inputs.map((i) => `${i.txid}:${i.vout}`));
|
|
3491
|
+
const cm = await this.wallet.getContractManager();
|
|
3492
|
+
const contractsWithVtxos = await cm.getContractsWithVtxos({
|
|
3493
|
+
type: ["default", "delegate"]
|
|
3494
|
+
});
|
|
3495
|
+
for (const { contract, vtxos } of contractsWithVtxos) {
|
|
3496
|
+
const serverPubKey = contract.params.serverPubKey;
|
|
3497
|
+
if (!serverPubKey) continue;
|
|
3498
|
+
if (classifyAgainstSignerSet(serverPubKey, signerSet, nowSeconds).status === "CURRENT") {
|
|
3499
|
+
continue;
|
|
3500
|
+
}
|
|
3501
|
+
for (const v of vtxos) {
|
|
3502
|
+
if (wanted.has(`${v.txid}:${v.vout}`)) return true;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
return false;
|
|
3506
|
+
}
|
|
2903
3507
|
// ========== Private Helpers ==========
|
|
2904
3508
|
/** Asserts sweep capability and returns the typed wallet. */
|
|
2905
3509
|
getSweepWallet() {
|
|
@@ -2926,6 +3530,16 @@ var VtxoManager = class _VtxoManager {
|
|
|
2926
3530
|
getArkProvider() {
|
|
2927
3531
|
return this.getSweepWallet().arkProvider;
|
|
2928
3532
|
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Read-only access to the ark provider for fetching server limits. Unlike
|
|
3535
|
+
* {@link getArkProvider}, this does not require full boarding-sweep
|
|
3536
|
+
* capability — recovery and renewal only need it to read `vtxoMaxAmount`.
|
|
3537
|
+
* Returns undefined when no provider is wired, which callers treat as
|
|
3538
|
+
* "no limit".
|
|
3539
|
+
*/
|
|
3540
|
+
getInfoProvider() {
|
|
3541
|
+
return this.wallet.arkProvider;
|
|
3542
|
+
}
|
|
2929
3543
|
/** Returns the Bitcoin network configuration from the wallet. */
|
|
2930
3544
|
getNetwork() {
|
|
2931
3545
|
return this.getSweepWallet().network;
|
|
@@ -3030,7 +3644,7 @@ var VtxoManager = class _VtxoManager {
|
|
|
3030
3644
|
* or doesn't carry the metadata.
|
|
3031
3645
|
*/
|
|
3032
3646
|
extractSpentOutpoint(error) {
|
|
3033
|
-
const ark =
|
|
3647
|
+
const ark = chunkC6OODRWD_cjs.maybeArkError(error);
|
|
3034
3648
|
if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
|
|
3035
3649
|
const raw = ark.metadata?.vtxo_outpoint;
|
|
3036
3650
|
if (typeof raw !== "string") return void 0;
|
|
@@ -3127,6 +3741,10 @@ var VtxoManager = class _VtxoManager {
|
|
|
3127
3741
|
}
|
|
3128
3742
|
}
|
|
3129
3743
|
}
|
|
3744
|
+
const migrationEnabled = this.settlementConfig !== false && (this.settlementConfig?.deprecatedSignerMigration ?? DEFAULT_SETTLEMENT_CONFIG.deprecatedSignerMigration);
|
|
3745
|
+
if (migrationEnabled && isMigrationCapable(this.wallet)) {
|
|
3746
|
+
await this.runMigrationPass();
|
|
3747
|
+
}
|
|
3130
3748
|
});
|
|
3131
3749
|
} catch (e) {
|
|
3132
3750
|
hadError = true;
|
|
@@ -3195,7 +3813,8 @@ var VtxoManager = class _VtxoManager {
|
|
|
3195
3813
|
return;
|
|
3196
3814
|
}
|
|
3197
3815
|
const dustAmount = getDustAmount(this.wallet);
|
|
3198
|
-
const
|
|
3816
|
+
const info = await this.getArkProvider().getInfo();
|
|
3817
|
+
const { fees, vtxoMaxAmount } = info;
|
|
3199
3818
|
const estimator = new Estimator(fees.intentFee);
|
|
3200
3819
|
let totalAmount = 0n;
|
|
3201
3820
|
const filteredBoarding = [];
|
|
@@ -3224,12 +3843,19 @@ var VtxoManager = class _VtxoManager {
|
|
|
3224
3843
|
if (inputFee.satoshis >= v.value) {
|
|
3225
3844
|
continue;
|
|
3226
3845
|
}
|
|
3846
|
+
const net = BigInt(v.value) - BigInt(inputFee.satoshis);
|
|
3847
|
+
if (vtxoMaxAmount >= 0n && totalAmount + net > vtxoMaxAmount) {
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3227
3850
|
filteredVtxos.push(v);
|
|
3228
|
-
totalAmount +=
|
|
3851
|
+
totalAmount += net;
|
|
3229
3852
|
}
|
|
3230
3853
|
if (filteredBoarding.length === 0 && filteredVtxos.length === 0) {
|
|
3231
3854
|
return;
|
|
3232
3855
|
}
|
|
3856
|
+
if (isMigrationCapable(this.wallet)) {
|
|
3857
|
+
await this.rotateForRecoverableInputs([...filteredBoarding, ...filteredVtxos], info);
|
|
3858
|
+
}
|
|
3233
3859
|
const arkAddress = await this.wallet.getAddress();
|
|
3234
3860
|
const outputFee = estimator.evalOffchainOutput({
|
|
3235
3861
|
amount: totalAmount,
|
|
@@ -3292,7 +3918,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
3292
3918
|
clearTimeout(timer);
|
|
3293
3919
|
}
|
|
3294
3920
|
const subscription = await this.contractEventsSubscriptionReady;
|
|
3295
|
-
this.contractEventsSubscription = void 0;
|
|
3296
3921
|
subscription?.();
|
|
3297
3922
|
})();
|
|
3298
3923
|
return this.disposePromise;
|
|
@@ -3833,15 +4458,17 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
|
|
|
3833
4458
|
txTime = getTxCreatedAt ? await getTxCreatedAt(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1 : vtxo.createdAt.getTime() + 1;
|
|
3834
4459
|
}
|
|
3835
4460
|
const assets = subtractAssets(allSpent, changes);
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
4461
|
+
if (txAmount !== 0 || assets) {
|
|
4462
|
+
sent.push({
|
|
4463
|
+
key: { ...txKey, arkTxid: vtxo.arkTxId },
|
|
4464
|
+
tag: "offchain",
|
|
4465
|
+
type: "SENT" /* TxSent */,
|
|
4466
|
+
amount: txAmount,
|
|
4467
|
+
settled: true,
|
|
4468
|
+
createdAt: txTime,
|
|
4469
|
+
...assets && { assets }
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
3845
4472
|
}
|
|
3846
4473
|
if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
|
|
3847
4474
|
const changes = fromOldestVtxo.filter(
|
|
@@ -3928,7 +4555,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3928
4555
|
const virtualCoins = await this.wallet.getVtxos({
|
|
3929
4556
|
withRecoverable: false
|
|
3930
4557
|
});
|
|
3931
|
-
const controlAssetRef = params.controlAssetId ?
|
|
4558
|
+
const controlAssetRef = params.controlAssetId ? chunkC6OODRWD_cjs.AssetRef.fromId(chunkC6OODRWD_cjs.AssetId.fromString(params.controlAssetId)) : null;
|
|
3932
4559
|
const coinSelection = selectVirtualCoins(virtualCoins, Number(this.wallet.dustAmount));
|
|
3933
4560
|
let totalBtcSelected = 0n;
|
|
3934
4561
|
const assetChanges = /* @__PURE__ */ new Map();
|
|
@@ -3941,8 +4568,8 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3941
4568
|
}
|
|
3942
4569
|
}
|
|
3943
4570
|
const groups = [];
|
|
3944
|
-
const issuedAssetOutput =
|
|
3945
|
-
const issuedAssetGroup =
|
|
4571
|
+
const issuedAssetOutput = chunkC6OODRWD_cjs.AssetOutput.create(0, params.amount);
|
|
4572
|
+
const issuedAssetGroup = chunkC6OODRWD_cjs.AssetGroup.create(
|
|
3946
4573
|
null,
|
|
3947
4574
|
controlAssetRef,
|
|
3948
4575
|
[],
|
|
@@ -3957,15 +4584,15 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3957
4584
|
for (const [inputIndex, assets] of assetInputs) {
|
|
3958
4585
|
for (const asset of assets) {
|
|
3959
4586
|
if (asset.assetId !== assetId) continue;
|
|
3960
|
-
changeInputs.push(
|
|
4587
|
+
changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
3961
4588
|
}
|
|
3962
4589
|
}
|
|
3963
4590
|
groups.push(
|
|
3964
|
-
|
|
3965
|
-
|
|
4591
|
+
chunkC6OODRWD_cjs.AssetGroup.create(
|
|
4592
|
+
chunkC6OODRWD_cjs.AssetId.fromString(assetId),
|
|
3966
4593
|
null,
|
|
3967
4594
|
changeInputs,
|
|
3968
|
-
[
|
|
4595
|
+
[chunkC6OODRWD_cjs.AssetOutput.create(0, amount)],
|
|
3969
4596
|
[]
|
|
3970
4597
|
)
|
|
3971
4598
|
);
|
|
@@ -3978,7 +4605,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3978
4605
|
script: outputAddress.pkScript,
|
|
3979
4606
|
amount: BigInt(totalBtcSelected)
|
|
3980
4607
|
},
|
|
3981
|
-
Extension.create([
|
|
4608
|
+
Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
|
|
3982
4609
|
];
|
|
3983
4610
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(
|
|
3984
4611
|
coinSelection.inputs,
|
|
@@ -3986,7 +4613,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3986
4613
|
);
|
|
3987
4614
|
return {
|
|
3988
4615
|
arkTxId: arkTxid,
|
|
3989
|
-
assetId:
|
|
4616
|
+
assetId: chunkC6OODRWD_cjs.AssetId.create(arkTxid, 0).toString()
|
|
3990
4617
|
};
|
|
3991
4618
|
}
|
|
3992
4619
|
/**
|
|
@@ -4058,16 +4685,16 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4058
4685
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4059
4686
|
for (const asset of assets) {
|
|
4060
4687
|
if (asset.assetId !== params.assetId) continue;
|
|
4061
|
-
reissueInputs.push(
|
|
4688
|
+
reissueInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4062
4689
|
}
|
|
4063
4690
|
}
|
|
4064
4691
|
const totalAssetAmount = assetToReissueAmount + params.amount;
|
|
4065
|
-
const reissueAssetIdObj =
|
|
4066
|
-
const reissueAssetGroup =
|
|
4692
|
+
const reissueAssetIdObj = chunkC6OODRWD_cjs.AssetId.fromString(params.assetId);
|
|
4693
|
+
const reissueAssetGroup = chunkC6OODRWD_cjs.AssetGroup.create(
|
|
4067
4694
|
reissueAssetIdObj,
|
|
4068
4695
|
null,
|
|
4069
4696
|
reissueInputs,
|
|
4070
|
-
[
|
|
4697
|
+
[chunkC6OODRWD_cjs.AssetOutput.create(0, totalAssetAmount)],
|
|
4071
4698
|
[]
|
|
4072
4699
|
);
|
|
4073
4700
|
const groups = [reissueAssetGroup];
|
|
@@ -4076,15 +4703,15 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4076
4703
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4077
4704
|
for (const asset of assets) {
|
|
4078
4705
|
if (asset.assetId !== assetId) continue;
|
|
4079
|
-
changeInputs.push(
|
|
4706
|
+
changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4080
4707
|
}
|
|
4081
4708
|
}
|
|
4082
4709
|
groups.push(
|
|
4083
|
-
|
|
4084
|
-
|
|
4710
|
+
chunkC6OODRWD_cjs.AssetGroup.create(
|
|
4711
|
+
chunkC6OODRWD_cjs.AssetId.fromString(assetId),
|
|
4085
4712
|
null,
|
|
4086
4713
|
changeInputs,
|
|
4087
|
-
[
|
|
4714
|
+
[chunkC6OODRWD_cjs.AssetOutput.create(0, amount)],
|
|
4088
4715
|
[]
|
|
4089
4716
|
)
|
|
4090
4717
|
);
|
|
@@ -4096,7 +4723,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4096
4723
|
script: outputAddress.pkScript,
|
|
4097
4724
|
amount: BigInt(totalBtcSelected)
|
|
4098
4725
|
},
|
|
4099
|
-
Extension.create([
|
|
4726
|
+
Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
|
|
4100
4727
|
];
|
|
4101
4728
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
4102
4729
|
return arkTxid;
|
|
@@ -4163,15 +4790,15 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4163
4790
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4164
4791
|
for (const asset of assets) {
|
|
4165
4792
|
if (asset.assetId !== assetId) continue;
|
|
4166
|
-
changeInputs.push(
|
|
4793
|
+
changeInputs.push(chunkC6OODRWD_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4167
4794
|
}
|
|
4168
4795
|
}
|
|
4169
4796
|
groups.push(
|
|
4170
|
-
|
|
4171
|
-
|
|
4797
|
+
chunkC6OODRWD_cjs.AssetGroup.create(
|
|
4798
|
+
chunkC6OODRWD_cjs.AssetId.fromString(assetId),
|
|
4172
4799
|
null,
|
|
4173
4800
|
changeInputs,
|
|
4174
|
-
amount > 0n ? [
|
|
4801
|
+
amount > 0n ? [chunkC6OODRWD_cjs.AssetOutput.create(0, amount)] : [],
|
|
4175
4802
|
[]
|
|
4176
4803
|
)
|
|
4177
4804
|
);
|
|
@@ -4183,7 +4810,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4183
4810
|
script: outputAddress.pkScript,
|
|
4184
4811
|
amount: BigInt(totalBtcSelected)
|
|
4185
4812
|
},
|
|
4186
|
-
Extension.create([
|
|
4813
|
+
Extension.create([chunkC6OODRWD_cjs.Packet.create(groups)]).txOut()
|
|
4187
4814
|
];
|
|
4188
4815
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
4189
4816
|
return arkTxid;
|
|
@@ -4208,7 +4835,7 @@ function castMetadata(metadata) {
|
|
|
4208
4835
|
} else {
|
|
4209
4836
|
throw new Error("Invalid metadata value type");
|
|
4210
4837
|
}
|
|
4211
|
-
md.push(
|
|
4838
|
+
md.push(chunkC6OODRWD_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
|
|
4212
4839
|
}
|
|
4213
4840
|
return md;
|
|
4214
4841
|
}
|
|
@@ -4493,7 +5120,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
|
|
|
4493
5120
|
expire_at: 0,
|
|
4494
5121
|
cosigners_public_keys: cosignerPubKeys
|
|
4495
5122
|
};
|
|
4496
|
-
const proof =
|
|
5123
|
+
const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, outputs);
|
|
4497
5124
|
const signedProof = await identity.sign(proof);
|
|
4498
5125
|
return {
|
|
4499
5126
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -6014,6 +6641,16 @@ function isDiscoverable(handler) {
|
|
|
6014
6641
|
}
|
|
6015
6642
|
|
|
6016
6643
|
// src/contracts/contractWatcher.ts
|
|
6644
|
+
function computeReconnectDelay(attempt, baseMs, maxMs) {
|
|
6645
|
+
return Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
|
|
6646
|
+
}
|
|
6647
|
+
var DEFAULT_CONTRACT_WATCHER_CONFIG = {
|
|
6648
|
+
failsafePollIntervalMs: 2e4,
|
|
6649
|
+
reconnectDelayMs: 1e3,
|
|
6650
|
+
maxReconnectDelayMs: 5e3,
|
|
6651
|
+
maxReconnectAttempts: 0
|
|
6652
|
+
// unlimited
|
|
6653
|
+
};
|
|
6017
6654
|
var ContractWatcher = class {
|
|
6018
6655
|
config;
|
|
6019
6656
|
contracts = /* @__PURE__ */ new Map();
|
|
@@ -6033,14 +6670,7 @@ var ContractWatcher = class {
|
|
|
6033
6670
|
*/
|
|
6034
6671
|
constructor(config) {
|
|
6035
6672
|
this.config = {
|
|
6036
|
-
|
|
6037
|
-
// 1 minute
|
|
6038
|
-
reconnectDelayMs: 1e3,
|
|
6039
|
-
// 1 second
|
|
6040
|
-
maxReconnectDelayMs: 3e4,
|
|
6041
|
-
// 30 seconds
|
|
6042
|
-
maxReconnectAttempts: 0,
|
|
6043
|
-
// unlimited
|
|
6673
|
+
...DEFAULT_CONTRACT_WATCHER_CONFIG,
|
|
6044
6674
|
...config
|
|
6045
6675
|
};
|
|
6046
6676
|
}
|
|
@@ -6233,7 +6863,7 @@ var ContractWatcher = class {
|
|
|
6233
6863
|
this.connectionState = "connected";
|
|
6234
6864
|
this.reconnectAttempts = 0;
|
|
6235
6865
|
this.listenLoop().catch((e) => {
|
|
6236
|
-
if (
|
|
6866
|
+
if (chunkC6OODRWD_cjs.isEventSourceError(e)) {
|
|
6237
6867
|
console.debug("ContractWatcher subscription disconnected; reconnecting");
|
|
6238
6868
|
} else {
|
|
6239
6869
|
console.error(e);
|
|
@@ -6268,8 +6898,9 @@ var ContractWatcher = class {
|
|
|
6268
6898
|
}
|
|
6269
6899
|
this.connectionState = "reconnecting";
|
|
6270
6900
|
this.reconnectAttempts++;
|
|
6271
|
-
const delay =
|
|
6272
|
-
this.
|
|
6901
|
+
const delay = computeReconnectDelay(
|
|
6902
|
+
this.reconnectAttempts,
|
|
6903
|
+
this.config.reconnectDelayMs,
|
|
6273
6904
|
this.config.maxReconnectDelayMs
|
|
6274
6905
|
);
|
|
6275
6906
|
this.reconnectTimeoutId = setTimeout(() => {
|
|
@@ -7640,6 +8271,22 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7640
8271
|
async drain() {
|
|
7641
8272
|
await this.chain.catch(() => void 0);
|
|
7642
8273
|
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Run `fn` on the rotator's serialization chain, so it cannot interleave
|
|
8276
|
+
* with a receive `rotate()`. Used by {@link Wallet.rotateServerSigner} to
|
|
8277
|
+
* serialize server-signer rotation against HD receive rotation: both
|
|
8278
|
+
* rebuild and swap `offchainTapscript`, so running them concurrently could
|
|
8279
|
+
* tear the wallet's visible receive state. The chain keeps advancing even
|
|
8280
|
+
* if `fn` rejects (its own caller still sees the rejection).
|
|
8281
|
+
*/
|
|
8282
|
+
runExclusive(fn) {
|
|
8283
|
+
const run = this.chain.catch(() => void 0).then(fn);
|
|
8284
|
+
this.chain = run.then(
|
|
8285
|
+
() => void 0,
|
|
8286
|
+
() => void 0
|
|
8287
|
+
);
|
|
8288
|
+
return run;
|
|
8289
|
+
}
|
|
7643
8290
|
/**
|
|
7644
8291
|
* Tear down the subscription first so no late `vtxo_received` event
|
|
7645
8292
|
* can queue work on a disposing wallet, then drain any in-flight
|
|
@@ -7997,7 +8644,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7997
8644
|
this.network = network;
|
|
7998
8645
|
this.onchainProvider = onchainProvider;
|
|
7999
8646
|
this.indexerProvider = indexerProvider;
|
|
8000
|
-
this.arkServerPublicKey = arkServerPublicKey;
|
|
8001
8647
|
this.dustAmount = dustAmount;
|
|
8002
8648
|
this.walletRepository = walletRepository;
|
|
8003
8649
|
this.contractRepository = contractRepository;
|
|
@@ -8014,6 +8660,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8014
8660
|
}
|
|
8015
8661
|
this._offchainTapscript = offchainTapscript;
|
|
8016
8662
|
this._boardingTapscript = boardingTapscript;
|
|
8663
|
+
this._arkServerPublicKey = arkServerPublicKey;
|
|
8017
8664
|
this.watcherConfig = watcherConfig;
|
|
8018
8665
|
this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
|
|
8019
8666
|
this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
|
|
@@ -8022,7 +8669,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8022
8669
|
_contractManagerInitializing;
|
|
8023
8670
|
watcherConfig;
|
|
8024
8671
|
_assetManager;
|
|
8025
|
-
_syncVtxosInflight;
|
|
8026
8672
|
walletContractTimelocks;
|
|
8027
8673
|
// Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
|
|
8028
8674
|
// from getVtxos() so concurrent callers (UI, VtxoManager auto-renewal,
|
|
@@ -8051,6 +8697,59 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8051
8697
|
* it stays the index-0 baseline for their lifetime.
|
|
8052
8698
|
*/
|
|
8053
8699
|
_boardingTapscript;
|
|
8700
|
+
/**
|
|
8701
|
+
* Backing field for the active server signer (x-only, 32 bytes). Read via
|
|
8702
|
+
* the public {@link arkServerPublicKey} getter; written only by
|
|
8703
|
+
* {@link Wallet.setArkServerPublicKeyForRotation}, the sanctioned
|
|
8704
|
+
* server-signer rotation write path (analogue of `_offchainTapscript`). It
|
|
8705
|
+
* is a *current value*, not a fixed constructor constant, because
|
|
8706
|
+
* mid-session server-signer rotation (plan §4) swaps it when arkd rotates
|
|
8707
|
+
* its active signer. Wallets that never span a rotation keep their
|
|
8708
|
+
* construction-time snapshot for their lifetime.
|
|
8709
|
+
*/
|
|
8710
|
+
_arkServerPublicKey;
|
|
8711
|
+
/**
|
|
8712
|
+
* x-only hex of the operator's deprecated signer keys (from
|
|
8713
|
+
* `ArkInfo.deprecatedSigners`), cached for the OFFLINE read/watch paths.
|
|
8714
|
+
* The boarding watch/history surfaces ({@link getBoardingAddresses},
|
|
8715
|
+
* {@link getBoardingTxs}) fan out over {current} ∪ this set so a deposit at
|
|
8716
|
+
* a boarding address minted under a now-rotated operator signer keeps being
|
|
8717
|
+
* watched. Refreshed from the server-info snapshot at construction (via the
|
|
8718
|
+
* create() factories) and on a detected signer change. Deliberately NOT
|
|
8719
|
+
* consulted by the spend path — {@link getBoardingUtxos} stays
|
|
8720
|
+
* current-signer-only (a deprecated-signer input in a plain settle() is
|
|
8721
|
+
* rejected; old-signer recovery goes through the migration API).
|
|
8722
|
+
*/
|
|
8723
|
+
_deprecatedSigners = /* @__PURE__ */ new Map();
|
|
8724
|
+
/**
|
|
8725
|
+
* Refresh the cached deprecated-signer set from a fresh server-info
|
|
8726
|
+
* snapshot. Called by the create() factories at construction and by the
|
|
8727
|
+
* server-info-change handler mid-session. Lenient: a malformed deprecated
|
|
8728
|
+
* entry is skipped, never fatal to wallet creation.
|
|
8729
|
+
*/
|
|
8730
|
+
refreshDeprecatedSigners(info) {
|
|
8731
|
+
const next = /* @__PURE__ */ new Map();
|
|
8732
|
+
for (const s of info.deprecatedSigners ?? []) {
|
|
8733
|
+
if (!s.pubkey) continue;
|
|
8734
|
+
try {
|
|
8735
|
+
next.set(toXOnlySignerHex(s.pubkey), s.cutoffDate ?? 0n);
|
|
8736
|
+
} catch (e) {
|
|
8737
|
+
console.warn("Skipping malformed deprecated signer pubkey", s.pubkey, e);
|
|
8738
|
+
}
|
|
8739
|
+
}
|
|
8740
|
+
this._deprecatedSigners = next;
|
|
8741
|
+
}
|
|
8742
|
+
/**
|
|
8743
|
+
* The signer set the boarding WATCH/HISTORY paths fan out over: the wallet's
|
|
8744
|
+
* current signer plus every cached deprecated signer. Distinct from the
|
|
8745
|
+
* spend path, which is current-signer-only.
|
|
8746
|
+
*/
|
|
8747
|
+
watchedBoardingSigners() {
|
|
8748
|
+
return /* @__PURE__ */ new Set([
|
|
8749
|
+
toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey)),
|
|
8750
|
+
...this._deprecatedSigners.keys()
|
|
8751
|
+
]);
|
|
8752
|
+
}
|
|
8054
8753
|
/**
|
|
8055
8754
|
* Currently-active receive tapscript. Read-only from the outside;
|
|
8056
8755
|
* mutated only via {@link Wallet.setOffchainTapscriptForRotation}
|
|
@@ -8059,6 +8758,16 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8059
8758
|
get offchainTapscript() {
|
|
8060
8759
|
return this._offchainTapscript;
|
|
8061
8760
|
}
|
|
8761
|
+
/**
|
|
8762
|
+
* The wallet's current active server signer (x-only, 32 bytes). Read-only
|
|
8763
|
+
* from the outside; mutated only via
|
|
8764
|
+
* {@link Wallet.setArkServerPublicKeyForRotation} during mid-session
|
|
8765
|
+
* server-signer rotation (plan §4). Single-valued for wallets that never
|
|
8766
|
+
* span a rotation.
|
|
8767
|
+
*/
|
|
8768
|
+
get arkServerPublicKey() {
|
|
8769
|
+
return this._arkServerPublicKey;
|
|
8770
|
+
}
|
|
8062
8771
|
/**
|
|
8063
8772
|
* The wallet's current boarding tapscript (the on-chain onboarding
|
|
8064
8773
|
* target). Read-only from the outside; mutated only via
|
|
@@ -8111,7 +8820,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8111
8820
|
*/
|
|
8112
8821
|
static async setupWalletConfig(config, pubKey) {
|
|
8113
8822
|
const arkadeServerUrl = getArkadeServerUrl(config);
|
|
8114
|
-
const arkProvider = config.arkProvider || new
|
|
8823
|
+
const arkProvider = config.arkProvider || new chunkC6OODRWD_cjs.RestArkProvider(arkadeServerUrl);
|
|
8115
8824
|
let indexerProvider = config.indexerProvider;
|
|
8116
8825
|
if (!indexerProvider) {
|
|
8117
8826
|
let indexerUrl = config.indexerUrl;
|
|
@@ -8128,7 +8837,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8128
8837
|
indexerUrl = arkadeServerUrl;
|
|
8129
8838
|
}
|
|
8130
8839
|
}
|
|
8131
|
-
indexerProvider = new
|
|
8840
|
+
indexerProvider = new chunkC6OODRWD_cjs.RestIndexerProvider(indexerUrl);
|
|
8132
8841
|
}
|
|
8133
8842
|
const info = await arkProvider.getInfo();
|
|
8134
8843
|
const network = chunkCMPJR3HS_cjs.getNetwork(info.network);
|
|
@@ -8217,7 +8926,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8217
8926
|
throw new Error("Invalid configured public key");
|
|
8218
8927
|
}
|
|
8219
8928
|
const setup = await _ReadonlyWallet.setupWalletConfig(config, pubkey);
|
|
8220
|
-
|
|
8929
|
+
const wallet = new _ReadonlyWallet(
|
|
8221
8930
|
config.identity,
|
|
8222
8931
|
setup.network,
|
|
8223
8932
|
setup.onchainProvider,
|
|
@@ -8232,6 +8941,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8232
8941
|
config.watcherConfig,
|
|
8233
8942
|
setup.walletContractTimelocks
|
|
8234
8943
|
);
|
|
8944
|
+
wallet.refreshDeprecatedSigners(setup.info);
|
|
8945
|
+
return wallet;
|
|
8235
8946
|
}
|
|
8236
8947
|
get arkAddress() {
|
|
8237
8948
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -8255,10 +8966,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8255
8966
|
* Return the wallet's combined onchain and offchain balances.
|
|
8256
8967
|
*/
|
|
8257
8968
|
async getBalance() {
|
|
8258
|
-
const [boardingUtxos, vtxos] = await Promise.all([
|
|
8969
|
+
const [boardingUtxos, vtxos, pendingOutpoints] = await Promise.all([
|
|
8259
8970
|
this.getBoardingUtxos(),
|
|
8260
|
-
this.getVtxos()
|
|
8971
|
+
this.getVtxos(),
|
|
8972
|
+
this.pendingRecoveryOutpoints()
|
|
8261
8973
|
]);
|
|
8974
|
+
const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
|
|
8262
8975
|
let confirmed = 0;
|
|
8263
8976
|
let unconfirmed = 0;
|
|
8264
8977
|
for (const utxo of boardingUtxos) {
|
|
@@ -8271,11 +8984,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8271
8984
|
let settled = 0;
|
|
8272
8985
|
let preconfirmed = 0;
|
|
8273
8986
|
let recoverable = 0;
|
|
8274
|
-
|
|
8275
|
-
|
|
8987
|
+
let pendingRecovery = 0;
|
|
8988
|
+
settled = vtxos.filter((coin) => coin.virtualStatus.state === "settled" && !isPendingRecovery(coin)).reduce((sum, coin) => sum + coin.value, 0);
|
|
8989
|
+
preconfirmed = vtxos.filter(
|
|
8990
|
+
(coin) => coin.virtualStatus.state === "preconfirmed" && !isPendingRecovery(coin)
|
|
8991
|
+
).reduce((sum, coin) => sum + coin.value, 0);
|
|
8276
8992
|
recoverable = vtxos.filter((coin) => isSpendable(coin) && coin.virtualStatus.state === "swept").reduce((sum, coin) => sum + coin.value, 0);
|
|
8993
|
+
pendingRecovery = vtxos.filter(isPendingRecovery).reduce((sum, coin) => sum + coin.value, 0);
|
|
8277
8994
|
const totalBoarding = confirmed + unconfirmed;
|
|
8278
|
-
const totalOffchain = settled + preconfirmed + recoverable;
|
|
8995
|
+
const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
|
|
8279
8996
|
const assetBalances = /* @__PURE__ */ new Map();
|
|
8280
8997
|
for (const vtxo of vtxos) {
|
|
8281
8998
|
if (!isSpendable(vtxo)) continue;
|
|
@@ -8300,6 +9017,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8300
9017
|
preconfirmed,
|
|
8301
9018
|
available: settled + preconfirmed,
|
|
8302
9019
|
recoverable,
|
|
9020
|
+
pendingRecovery,
|
|
8303
9021
|
total: totalBoarding + totalOffchain,
|
|
8304
9022
|
assets
|
|
8305
9023
|
};
|
|
@@ -8326,6 +9044,23 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8326
9044
|
return !!(f.withUnrolled && vtxo.isUnrolled);
|
|
8327
9045
|
});
|
|
8328
9046
|
}
|
|
9047
|
+
/**
|
|
9048
|
+
* Outpoints of VTXOs whose deprecated signer is past its cutoff (EXPIRED) and
|
|
9049
|
+
* which have not yet been swept — unspendable until they recover. Offline:
|
|
9050
|
+
* classifies the repo's contracts against the cached signer set (active +
|
|
9051
|
+
* {@link _deprecatedSigners}, cutoffs included). Empty fast-path when no
|
|
9052
|
+
* signer is deprecated. Consumed by {@link getBalance} (the `pendingRecovery`
|
|
9053
|
+
* bucket) and the send coin-selection path so neither counts nor spends them.
|
|
9054
|
+
*/
|
|
9055
|
+
async pendingRecoveryOutpoints() {
|
|
9056
|
+
if (this._deprecatedSigners.size === 0) return /* @__PURE__ */ new Set();
|
|
9057
|
+
const contractManager = await this.getContractManager();
|
|
9058
|
+
const contractsWithVtxos = await contractManager.getContractsWithVtxos();
|
|
9059
|
+
return selectPendingRecoveryOutpoints(contractsWithVtxos, {
|
|
9060
|
+
active: toXOnlySignerHex(base.hex.encode(this.offchainTapscript.options.serverPubKey)),
|
|
9061
|
+
deprecated: this._deprecatedSigners
|
|
9062
|
+
});
|
|
9063
|
+
}
|
|
8329
9064
|
/**
|
|
8330
9065
|
* Return wallet transaction history derived from Arkade state and boarding transactions.
|
|
8331
9066
|
*/
|
|
@@ -8352,7 +9087,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8352
9087
|
* surfaced (plan §6-IV); {@link getBoardingAddress} stays single-valued.
|
|
8353
9088
|
*/
|
|
8354
9089
|
async getBoardingAddresses() {
|
|
8355
|
-
const tapscripts = await this.getBoardingTapscripts();
|
|
9090
|
+
const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
|
|
8356
9091
|
return tapscripts.map((t) => t.onchainAddress(this.network));
|
|
8357
9092
|
}
|
|
8358
9093
|
/**
|
|
@@ -8362,7 +9097,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8362
9097
|
async getBoardingTxs() {
|
|
8363
9098
|
const utxos = [];
|
|
8364
9099
|
const commitmentsToIgnore = /* @__PURE__ */ new Set();
|
|
8365
|
-
const tapscripts = await this.getBoardingTapscripts();
|
|
9100
|
+
const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
|
|
8366
9101
|
const outspendCache = /* @__PURE__ */ new Map();
|
|
8367
9102
|
for (const tapscript of tapscripts) {
|
|
8368
9103
|
const boardingAddress = tapscript.onchainAddress(this.network);
|
|
@@ -8437,8 +9172,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8437
9172
|
* Always includes the index-0 baseline (identity x-only key), which covers
|
|
8438
9173
|
* the degenerate equal-delay case where the index-0 boarding row is
|
|
8439
9174
|
* coalesced onto a `default` row and so isn't a `boarding`-typed contract.
|
|
9175
|
+
*
|
|
9176
|
+
* @param allowedSigners - Optional set of x-only-hex server keys whose
|
|
9177
|
+
* persisted boarding rows are included. Defaults to `{current x-only
|
|
9178
|
+
* signer}`, preserving today's current-signer-only discovery (and the
|
|
9179
|
+
* foreign-ASP guard). The deprecated-signer migration path widens this to
|
|
9180
|
+
* reach old-signer boarding addresses. The index-0 baseline and the
|
|
9181
|
+
* current display tapscript are always included regardless of the set.
|
|
8440
9182
|
*/
|
|
8441
|
-
async getBoardingTapscripts() {
|
|
9183
|
+
async getBoardingTapscripts(allowedSigners) {
|
|
8442
9184
|
const byScript = /* @__PURE__ */ new Map();
|
|
8443
9185
|
const add = (s) => byScript.set(base.hex.encode(s.pkScript), s);
|
|
8444
9186
|
const boardingCsv = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
@@ -8451,11 +9193,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8451
9193
|
);
|
|
8452
9194
|
add(this.boardingTapscript);
|
|
8453
9195
|
const serverPubKeyHex = base.hex.encode(this.boardingTapscript.options.serverPubKey);
|
|
9196
|
+
const allowed = allowedSigners ?? /* @__PURE__ */ new Set([toXOnlySignerHex(serverPubKeyHex)]);
|
|
8454
9197
|
const boardingContracts = await this.contractRepository.getContracts({
|
|
8455
9198
|
type: ["boarding"]
|
|
8456
9199
|
});
|
|
8457
9200
|
for (const c of boardingContracts) {
|
|
8458
|
-
if (c.params.serverPubKey
|
|
9201
|
+
if (!allowed.has(toXOnlySignerHex(c.params.serverPubKey))) continue;
|
|
8459
9202
|
try {
|
|
8460
9203
|
add(chunkGUTKJMSF_cjs.BoardingContractHandler.createScript(c.params));
|
|
8461
9204
|
} catch (e) {
|
|
@@ -8465,23 +9208,61 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8465
9208
|
return [...byScript.values()];
|
|
8466
9209
|
}
|
|
8467
9210
|
/**
|
|
8468
|
-
* Fetch and cache onchain inputs (UTXOs) received at the
|
|
8469
|
-
*
|
|
8470
|
-
*
|
|
8471
|
-
*
|
|
8472
|
-
*
|
|
9211
|
+
* Fetch and cache onchain inputs (UTXOs) received at the boarding addresses
|
|
9212
|
+
* of the given signer set, grouped per boarding address so the caller keeps
|
|
9213
|
+
* the address↔signer association that {@link ExtendedCoin} cannot carry
|
|
9214
|
+
* (it retains only the encoded leaves/tapTree the spend needs, not the
|
|
9215
|
+
* `DefaultVtxo.Script` and its `serverPubKey`/CSV delay).
|
|
9216
|
+
*
|
|
9217
|
+
* Per group it does exactly what {@link getBoardingUtxos} does per tapscript:
|
|
9218
|
+
* `getCoins` → {@link extendCoinWithTapscript} → `saveUtxos`. Offline-first:
|
|
9219
|
+
* it does not call `getInfo()`; the caller supplies the allowed signer set,
|
|
9220
|
+
* so the only network calls are the per-address `getCoins`.
|
|
9221
|
+
*
|
|
9222
|
+
* @param allowedSigners - x-only-hex server keys whose boarding addresses to
|
|
9223
|
+
* fetch (passed through to {@link getBoardingTapscripts}).
|
|
8473
9224
|
*/
|
|
8474
|
-
async
|
|
8475
|
-
const tapscripts = await this.getBoardingTapscripts();
|
|
8476
|
-
const
|
|
9225
|
+
async getBoardingUtxosForSigners(allowedSigners) {
|
|
9226
|
+
const tapscripts = await this.getBoardingTapscripts(allowedSigners);
|
|
9227
|
+
const groups = [];
|
|
8477
9228
|
for (const tapscript of tapscripts) {
|
|
8478
9229
|
const address = tapscript.onchainAddress(this.network);
|
|
8479
9230
|
const coins = await this.onchainProvider.getCoins(address);
|
|
8480
9231
|
const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
|
|
8481
9232
|
await this.walletRepository.saveUtxos(address, utxos);
|
|
8482
|
-
|
|
9233
|
+
groups.push({
|
|
9234
|
+
tapscript,
|
|
9235
|
+
// Normalize so the group key matches the axis/contract x-only
|
|
9236
|
+
// form regardless of how the tapscript's key was stored.
|
|
9237
|
+
serverPubKey: toXOnlySignerHex(base.hex.encode(tapscript.options.serverPubKey)),
|
|
9238
|
+
// Per-row CSV delay decoded from THIS tapscript's exit leaf —
|
|
9239
|
+
// not the wallet's current boarding timelock, which a signer
|
|
9240
|
+
// rotation may have changed.
|
|
9241
|
+
csvTimelock: chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(tapscript.exitScript)).params.timelock,
|
|
9242
|
+
coins: utxos
|
|
9243
|
+
});
|
|
8483
9244
|
}
|
|
8484
|
-
return
|
|
9245
|
+
return groups;
|
|
9246
|
+
}
|
|
9247
|
+
/**
|
|
9248
|
+
* Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding
|
|
9249
|
+
* addresses — the current address plus any historical rotated boarding
|
|
9250
|
+
* addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is
|
|
9251
|
+
* annotated with the tapscript of the address it actually sits on, so the
|
|
9252
|
+
* spending path forfeits / exits it with the correct per-index leaves.
|
|
9253
|
+
*
|
|
9254
|
+
* Current-signer only: a flatten of {@link getBoardingUtxosForSigners} over
|
|
9255
|
+
* the wallet's current signer, so the two paths cannot drift. Old-signer
|
|
9256
|
+
* boarding recovery goes through the deprecated-signer migration API
|
|
9257
|
+
* instead (it would otherwise pull EXPIRED-signer inputs into a plain
|
|
9258
|
+
* `settle()` that the server must reject).
|
|
9259
|
+
*/
|
|
9260
|
+
async getBoardingUtxos() {
|
|
9261
|
+
const currentOnly = /* @__PURE__ */ new Set([
|
|
9262
|
+
toXOnlySignerHex(base.hex.encode(this.boardingTapscript.options.serverPubKey))
|
|
9263
|
+
]);
|
|
9264
|
+
const groups = await this.getBoardingUtxosForSigners(currentOnly);
|
|
9265
|
+
return groups.flatMap((g) => g.coins);
|
|
8485
9266
|
}
|
|
8486
9267
|
/**
|
|
8487
9268
|
* Subscribe to onchain and offchain notifications for newly received funds.
|
|
@@ -8688,64 +9469,82 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8688
9469
|
watcherConfig: this.watcherConfig
|
|
8689
9470
|
});
|
|
8690
9471
|
const baselinePubkey = await this.identity.xOnlyPublicKey();
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
9472
|
+
const delegatePubKey = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
|
|
9473
|
+
const baselineSigners = [
|
|
9474
|
+
this.offchainTapscript.options.serverPubKey,
|
|
9475
|
+
...[...this._deprecatedSigners.keys()].map((h) => base.hex.decode(h))
|
|
9476
|
+
];
|
|
9477
|
+
const seenBaselineScripts = /* @__PURE__ */ new Set();
|
|
9478
|
+
for (const serverPubKey of baselineSigners) {
|
|
9479
|
+
for (const csvTimelock of this.walletContractTimelocks) {
|
|
9480
|
+
const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
|
|
9481
|
+
const defaultScript = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
9482
|
+
pubKey: baselinePubkey,
|
|
9483
|
+
serverPubKey,
|
|
9484
|
+
csvTimelock
|
|
9485
|
+
});
|
|
9486
|
+
const defaultScriptHex = base.hex.encode(defaultScript.pkScript);
|
|
9487
|
+
if (!seenBaselineScripts.has(defaultScriptHex)) {
|
|
9488
|
+
seenBaselineScripts.add(defaultScriptHex);
|
|
9489
|
+
await ensureWalletContract(manager, {
|
|
9490
|
+
type: "default",
|
|
9491
|
+
params: {
|
|
9492
|
+
pubKey: base.hex.encode(defaultScript.options.pubKey),
|
|
9493
|
+
serverPubKey: base.hex.encode(serverPubKey),
|
|
9494
|
+
csvTimelock: csvTimelockStr
|
|
9495
|
+
},
|
|
9496
|
+
script: defaultScriptHex,
|
|
9497
|
+
address: defaultScript.address(this.network.hrp, serverPubKey).encode(),
|
|
9498
|
+
state: "active"
|
|
9499
|
+
});
|
|
9500
|
+
}
|
|
9501
|
+
if (delegatePubKey) {
|
|
9502
|
+
const delegateScript = new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
|
|
9503
|
+
pubKey: baselinePubkey,
|
|
9504
|
+
serverPubKey,
|
|
9505
|
+
delegatePubKey,
|
|
9506
|
+
csvTimelock
|
|
9507
|
+
});
|
|
9508
|
+
const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
|
|
9509
|
+
if (seenBaselineScripts.has(delegateScriptHex)) continue;
|
|
9510
|
+
seenBaselineScripts.add(delegateScriptHex);
|
|
9511
|
+
await manager.createContract({
|
|
9512
|
+
type: "delegate",
|
|
9513
|
+
params: {
|
|
9514
|
+
pubKey: base.hex.encode(delegateScript.options.pubKey),
|
|
9515
|
+
serverPubKey: base.hex.encode(serverPubKey),
|
|
9516
|
+
delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
|
|
9517
|
+
csvTimelock: csvTimelockStr
|
|
9518
|
+
},
|
|
9519
|
+
script: delegateScriptHex,
|
|
9520
|
+
address: delegateScript.address(this.network.hrp, serverPubKey).encode(),
|
|
9521
|
+
state: "active"
|
|
9522
|
+
});
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
}
|
|
9526
|
+
const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
9527
|
+
for (const serverPubKey of baselineSigners) {
|
|
9528
|
+
const baselineBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
8694
9529
|
pubKey: baselinePubkey,
|
|
8695
|
-
serverPubKey
|
|
8696
|
-
csvTimelock
|
|
9530
|
+
serverPubKey,
|
|
9531
|
+
csvTimelock: boardingCsvTimelock
|
|
8697
9532
|
});
|
|
8698
|
-
const
|
|
9533
|
+
const boardingScriptHex = base.hex.encode(baselineBoarding.pkScript);
|
|
9534
|
+
if (seenBaselineScripts.has(boardingScriptHex)) continue;
|
|
9535
|
+
seenBaselineScripts.add(boardingScriptHex);
|
|
8699
9536
|
await ensureWalletContract(manager, {
|
|
8700
|
-
type: "
|
|
9537
|
+
type: "boarding",
|
|
8701
9538
|
params: {
|
|
8702
|
-
pubKey: base.hex.encode(
|
|
8703
|
-
serverPubKey: base.hex.encode(
|
|
8704
|
-
csvTimelock:
|
|
9539
|
+
pubKey: base.hex.encode(baselineBoarding.options.pubKey),
|
|
9540
|
+
serverPubKey: base.hex.encode(serverPubKey),
|
|
9541
|
+
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
|
|
8705
9542
|
},
|
|
8706
|
-
script:
|
|
8707
|
-
address:
|
|
9543
|
+
script: boardingScriptHex,
|
|
9544
|
+
address: baselineBoarding.address(this.network.hrp, serverPubKey).encode(),
|
|
8708
9545
|
state: "active"
|
|
8709
9546
|
});
|
|
8710
|
-
if (this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
|
|
8711
|
-
const delegateScript = new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
|
|
8712
|
-
pubKey: baselinePubkey,
|
|
8713
|
-
serverPubKey: this.offchainTapscript.options.serverPubKey,
|
|
8714
|
-
delegatePubKey: this.offchainTapscript.options.delegatePubKey,
|
|
8715
|
-
csvTimelock
|
|
8716
|
-
});
|
|
8717
|
-
const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
|
|
8718
|
-
await manager.createContract({
|
|
8719
|
-
type: "delegate",
|
|
8720
|
-
params: {
|
|
8721
|
-
pubKey: base.hex.encode(delegateScript.options.pubKey),
|
|
8722
|
-
serverPubKey: base.hex.encode(delegateScript.options.serverPubKey),
|
|
8723
|
-
delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
|
|
8724
|
-
csvTimelock: csvTimelockStr
|
|
8725
|
-
},
|
|
8726
|
-
script: delegateScriptHex,
|
|
8727
|
-
address: delegateScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8728
|
-
state: "active"
|
|
8729
|
-
});
|
|
8730
|
-
}
|
|
8731
9547
|
}
|
|
8732
|
-
const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
8733
|
-
const baselineBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
8734
|
-
pubKey: baselinePubkey,
|
|
8735
|
-
serverPubKey: this.boardingTapscript.options.serverPubKey,
|
|
8736
|
-
csvTimelock: boardingCsvTimelock
|
|
8737
|
-
});
|
|
8738
|
-
await ensureWalletContract(manager, {
|
|
8739
|
-
type: "boarding",
|
|
8740
|
-
params: {
|
|
8741
|
-
pubKey: base.hex.encode(baselineBoarding.options.pubKey),
|
|
8742
|
-
serverPubKey: base.hex.encode(baselineBoarding.options.serverPubKey),
|
|
8743
|
-
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
|
|
8744
|
-
},
|
|
8745
|
-
script: base.hex.encode(baselineBoarding.pkScript),
|
|
8746
|
-
address: baselineBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8747
|
-
state: "active"
|
|
8748
|
-
});
|
|
8749
9548
|
return manager;
|
|
8750
9549
|
}
|
|
8751
9550
|
/** Dispose wallet-owned managers and release background resources. */
|
|
@@ -8778,7 +9577,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8778
9577
|
walletContractTimelocks
|
|
8779
9578
|
);
|
|
8780
9579
|
this.arkProvider = arkProvider;
|
|
8781
|
-
this.serverUnrollScript = serverUnrollScript;
|
|
8782
9580
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
8783
9581
|
this.forfeitPubkey = forfeitPubkey;
|
|
8784
9582
|
this.identity = identity;
|
|
@@ -8799,6 +9597,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8799
9597
|
this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
|
|
8800
9598
|
}
|
|
8801
9599
|
this._delegateManager = delegateProvider ? new DelegateManagerImpl(delegateProvider, arkProvider, identity) : void 0;
|
|
9600
|
+
this._serverUnrollScript = serverUnrollScript;
|
|
8802
9601
|
this._receiveRotator = receiveRotator;
|
|
8803
9602
|
this._descriptorProvider = descriptorProvider;
|
|
8804
9603
|
this._signerRouter = new InputSignerRouter({
|
|
@@ -8824,6 +9623,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8824
9623
|
* the contract manager is up first.
|
|
8825
9624
|
*/
|
|
8826
9625
|
_receiveRotator;
|
|
9626
|
+
/**
|
|
9627
|
+
* Unsubscribe handle for the arkProvider's `onServerInfoChanged` stream
|
|
9628
|
+
* (mid-session signer-rotation detection). Torn down in {@link dispose}.
|
|
9629
|
+
*/
|
|
9630
|
+
_serverInfoUnsub;
|
|
9631
|
+
/**
|
|
9632
|
+
* Tail of the serialized {@link handleServerInfoChanged} chain. Each
|
|
9633
|
+
* `onServerInfoChanged` event chains onto it so handlers run one at a time,
|
|
9634
|
+
* and {@link dispose} awaits it so an in-flight re-derive/rotation settles
|
|
9635
|
+
* before the contract manager is torn down underneath it.
|
|
9636
|
+
*/
|
|
9637
|
+
_serverInfoInFlight = Promise.resolve();
|
|
9638
|
+
/**
|
|
9639
|
+
* React to a mid-session server-info change (driven by the arkProvider's
|
|
9640
|
+
* `DIGEST_MISMATCH` detection). First refresh the cached deprecated-signer
|
|
9641
|
+
* set so the boarding WATCH path immediately widens to the just-deprecated
|
|
9642
|
+
* signer, then — only if the active signer actually changed — rotate the
|
|
9643
|
+
* wallet onto it via {@link rotateServerSigner} (re-deriving the offchain +
|
|
9644
|
+
* boarding display tapscripts and registering the current-signer rows).
|
|
9645
|
+
* Old-signer rows stay active, so existing funds remain watched. Failures
|
|
9646
|
+
* are logged, never thrown back into the provider's emit loop.
|
|
9647
|
+
*/
|
|
9648
|
+
async handleServerInfoChanged(info) {
|
|
9649
|
+
this.refreshDeprecatedSigners(info);
|
|
9650
|
+
try {
|
|
9651
|
+
const newActive = toXOnlySignerHex(info.signerPubkey);
|
|
9652
|
+
const current = toXOnlySignerHex(base.hex.encode(this.arkServerPublicKey));
|
|
9653
|
+
if (newActive !== current) {
|
|
9654
|
+
await this.rotateServerSigner(
|
|
9655
|
+
base.hex.decode(info.signerPubkey),
|
|
9656
|
+
info.checkpointTapscript
|
|
9657
|
+
);
|
|
9658
|
+
}
|
|
9659
|
+
} catch (e) {
|
|
9660
|
+
console.warn("server-signer rotation on info change failed", e);
|
|
9661
|
+
}
|
|
9662
|
+
}
|
|
8827
9663
|
_receiveRotatorInstalled = false;
|
|
8828
9664
|
/**
|
|
8829
9665
|
* Descriptor-aware signer used by {@link _signerRouter} to sign
|
|
@@ -8853,6 +9689,44 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8853
9689
|
this._boardingTapscript = tapscript;
|
|
8854
9690
|
this.notifyBoardingRotation();
|
|
8855
9691
|
}
|
|
9692
|
+
/**
|
|
9693
|
+
* @internal Sole write path for `arkServerPublicKey` after construction.
|
|
9694
|
+
* Called by {@link Wallet.rotateServerSigner} once the rotated offchain and
|
|
9695
|
+
* boarding contract rows have been persisted. External code must treat
|
|
9696
|
+
* `arkServerPublicKey` as read-only.
|
|
9697
|
+
*/
|
|
9698
|
+
setArkServerPublicKeyForRotation(serverPubKey) {
|
|
9699
|
+
this._arkServerPublicKey = serverPubKey;
|
|
9700
|
+
}
|
|
9701
|
+
/**
|
|
9702
|
+
* Output script for checkpoint transactions, decoded from the server's
|
|
9703
|
+
* `checkpointTapscript`. Server-controlled state: pinned at construction
|
|
9704
|
+
* and re-sourced from a fresh `ArkInfo` on server-signer rotation. Read it
|
|
9705
|
+
* through {@link serverUnrollScript}; write it only through
|
|
9706
|
+
* {@link setServerUnrollScriptForRotation}.
|
|
9707
|
+
*/
|
|
9708
|
+
_serverUnrollScript;
|
|
9709
|
+
get serverUnrollScript() {
|
|
9710
|
+
return this._serverUnrollScript;
|
|
9711
|
+
}
|
|
9712
|
+
/**
|
|
9713
|
+
* @internal Sole write path for `serverUnrollScript` after construction.
|
|
9714
|
+
* Called by {@link Wallet._doRotateServerSigner} with the checkpoint script
|
|
9715
|
+
* sourced from the fresh `ArkInfo` that triggered the rotation, so the send
|
|
9716
|
+
* path builds checkpoints against the new server epoch. External code must
|
|
9717
|
+
* treat `serverUnrollScript` as read-only.
|
|
9718
|
+
*/
|
|
9719
|
+
setServerUnrollScriptForRotation(script) {
|
|
9720
|
+
this._serverUnrollScript = script;
|
|
9721
|
+
}
|
|
9722
|
+
/**
|
|
9723
|
+
* Serializes {@link rotateServerSigner} for static / non-HD wallets (which
|
|
9724
|
+
* have no {@link WalletReceiveRotator} chain to ride). Coalesces concurrent
|
|
9725
|
+
* migration passes so two callers cannot both rebuild and swap the
|
|
9726
|
+
* tapscripts. HD wallets serialize on the rotator's chain instead, via
|
|
9727
|
+
* {@link WalletReceiveRotator.runExclusive}.
|
|
9728
|
+
*/
|
|
9729
|
+
_serverRotationChain = Promise.resolve();
|
|
8856
9730
|
/**
|
|
8857
9731
|
* Allocate and return a *fresh* on-chain boarding address, rotating the
|
|
8858
9732
|
* wallet's current boarding tapscript to a new HD index.
|
|
@@ -8909,6 +9783,126 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8909
9783
|
this.setBoardingTapscriptForRotation(newBoarding);
|
|
8910
9784
|
return newBoarding.onchainAddress(this.network);
|
|
8911
9785
|
}
|
|
9786
|
+
/**
|
|
9787
|
+
* Mid-session server-signer rotation (plan §4). When arkd rotates its
|
|
9788
|
+
* active signer mid-session — the case the long-lived service worker and
|
|
9789
|
+
* Expo background processes that own automatic migration must handle — a
|
|
9790
|
+
* wallet constructed before the rotation keeps deriving old-signer receive
|
|
9791
|
+
* addresses. Building a migration output to such an address would produce a
|
|
9792
|
+
* VTXO the server must reject, so the wallet must first re-derive its own
|
|
9793
|
+
* receive state under the new active signer.
|
|
9794
|
+
*
|
|
9795
|
+
* Follows the {@link WalletReceiveRotator.rotate} write-path pattern with
|
|
9796
|
+
* the server key swapped instead of the user key: build the new offchain
|
|
9797
|
+
* and boarding tapscripts locally (preserving every other option),
|
|
9798
|
+
* register the matching `default`/`delegate` and `boarding` contract rows
|
|
9799
|
+
* through {@link ContractManager.createContract}, and only then commit the
|
|
9800
|
+
* new tapscripts and server key to the wallet's visible state. The signing
|
|
9801
|
+
* metadata of the current receive/boarding rows is carried onto the new
|
|
9802
|
+
* rows so a rotated (descriptor-backed) receive pubkey can still sign.
|
|
9803
|
+
*
|
|
9804
|
+
* The old-signer contract rows are intentionally left `active` and watched
|
|
9805
|
+
* — they are exactly the deprecated-signer contracts the migration pass
|
|
9806
|
+
* drains. Idempotent: a no-op when the wallet already tracks `xonly`.
|
|
9807
|
+
*
|
|
9808
|
+
* Serialized against HD receive rotation so the two paths (both of which
|
|
9809
|
+
* rebuild and swap `offchainTapscript`) cannot interleave.
|
|
9810
|
+
*
|
|
9811
|
+
* @internal Invoked by the {@link VtxoManager} migration pass; not part of
|
|
9812
|
+
* the stable public API.
|
|
9813
|
+
*/
|
|
9814
|
+
async rotateServerSigner(newServerPubKey, checkpointTapscript) {
|
|
9815
|
+
const xonly = toXOnlyPubKey(newServerPubKey);
|
|
9816
|
+
let newServerUnrollScript;
|
|
9817
|
+
try {
|
|
9818
|
+
newServerUnrollScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(base.hex.decode(checkpointTapscript));
|
|
9819
|
+
} catch (e) {
|
|
9820
|
+
throw new Error("Invalid checkpointTapscript from server");
|
|
9821
|
+
}
|
|
9822
|
+
if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
|
|
9823
|
+
if (this._receiveRotator) {
|
|
9824
|
+
await this._receiveRotator.runExclusive(
|
|
9825
|
+
() => this._doRotateServerSigner(xonly, newServerUnrollScript)
|
|
9826
|
+
);
|
|
9827
|
+
return;
|
|
9828
|
+
}
|
|
9829
|
+
const run = this._serverRotationChain.catch(() => void 0).then(() => this._doRotateServerSigner(xonly, newServerUnrollScript));
|
|
9830
|
+
this._serverRotationChain = run.then(
|
|
9831
|
+
() => void 0,
|
|
9832
|
+
() => void 0
|
|
9833
|
+
);
|
|
9834
|
+
return run;
|
|
9835
|
+
}
|
|
9836
|
+
async _doRotateServerSigner(xonly, newServerUnrollScript) {
|
|
9837
|
+
if (utils_js.equalBytes(xonly, this.arkServerPublicKey)) return;
|
|
9838
|
+
const manager = await this.getContractManager();
|
|
9839
|
+
const [currentOffchainRow] = await manager.getContracts({
|
|
9840
|
+
script: this.defaultContractScript
|
|
9841
|
+
});
|
|
9842
|
+
const currentBoardingScript = base.hex.encode(this._boardingTapscript.pkScript);
|
|
9843
|
+
const [currentBoardingRow] = await manager.getContracts({
|
|
9844
|
+
script: currentBoardingScript
|
|
9845
|
+
});
|
|
9846
|
+
const newOffchain = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? new chunkGUTKJMSF_cjs.DelegateVtxo.Script({
|
|
9847
|
+
...this.offchainTapscript.options,
|
|
9848
|
+
serverPubKey: xonly
|
|
9849
|
+
}) : new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
9850
|
+
...this.offchainTapscript.options,
|
|
9851
|
+
serverPubKey: xonly
|
|
9852
|
+
});
|
|
9853
|
+
const newBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
9854
|
+
...this._boardingTapscript.options,
|
|
9855
|
+
serverPubKey: xonly
|
|
9856
|
+
});
|
|
9857
|
+
const offchainCsv = chunkCMPJR3HS_cjs.timelockToSequence(newOffchain.options.csvTimelock).toString();
|
|
9858
|
+
const newOffchainScript = base.hex.encode(newOffchain.pkScript);
|
|
9859
|
+
const newOffchainAddress = newOffchain.address(this.network.hrp, xonly).encode();
|
|
9860
|
+
if (newOffchain instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
|
|
9861
|
+
await manager.createContract({
|
|
9862
|
+
type: "delegate",
|
|
9863
|
+
params: {
|
|
9864
|
+
pubKey: base.hex.encode(newOffchain.options.pubKey),
|
|
9865
|
+
serverPubKey: base.hex.encode(xonly),
|
|
9866
|
+
delegatePubKey: base.hex.encode(newOffchain.options.delegatePubKey),
|
|
9867
|
+
csvTimelock: offchainCsv
|
|
9868
|
+
},
|
|
9869
|
+
script: newOffchainScript,
|
|
9870
|
+
address: newOffchainAddress,
|
|
9871
|
+
state: "active",
|
|
9872
|
+
metadata: currentOffchainRow?.metadata
|
|
9873
|
+
});
|
|
9874
|
+
} else {
|
|
9875
|
+
await manager.createContract({
|
|
9876
|
+
type: "default",
|
|
9877
|
+
params: {
|
|
9878
|
+
pubKey: base.hex.encode(newOffchain.options.pubKey),
|
|
9879
|
+
serverPubKey: base.hex.encode(xonly),
|
|
9880
|
+
csvTimelock: offchainCsv
|
|
9881
|
+
},
|
|
9882
|
+
script: newOffchainScript,
|
|
9883
|
+
address: newOffchainAddress,
|
|
9884
|
+
state: "active",
|
|
9885
|
+
metadata: currentOffchainRow?.metadata
|
|
9886
|
+
});
|
|
9887
|
+
}
|
|
9888
|
+
const boardingCsv = newBoarding.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
9889
|
+
await manager.createContract({
|
|
9890
|
+
type: "boarding",
|
|
9891
|
+
params: {
|
|
9892
|
+
pubKey: base.hex.encode(newBoarding.options.pubKey),
|
|
9893
|
+
serverPubKey: base.hex.encode(xonly),
|
|
9894
|
+
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsv).toString()
|
|
9895
|
+
},
|
|
9896
|
+
script: base.hex.encode(newBoarding.pkScript),
|
|
9897
|
+
address: newBoarding.address(this.network.hrp, xonly).encode(),
|
|
9898
|
+
state: "active",
|
|
9899
|
+
metadata: currentBoardingRow?.metadata
|
|
9900
|
+
});
|
|
9901
|
+
this.setOffchainTapscriptForRotation(newOffchain);
|
|
9902
|
+
this.setBoardingTapscriptForRotation(newBoarding);
|
|
9903
|
+
this.setArkServerPublicKeyForRotation(xonly);
|
|
9904
|
+
this.setServerUnrollScriptForRotation(newServerUnrollScript);
|
|
9905
|
+
}
|
|
8912
9906
|
/**
|
|
8913
9907
|
* Async mutex that serializes all operations submitting VTXOs to the Arkade
|
|
8914
9908
|
* server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
|
|
@@ -9069,6 +10063,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9069
10063
|
}
|
|
9070
10064
|
async dispose() {
|
|
9071
10065
|
await this._restoreInFlight?.catch(() => void 0);
|
|
10066
|
+
this._serverInfoUnsub?.();
|
|
10067
|
+
this._serverInfoUnsub = void 0;
|
|
10068
|
+
await this._serverInfoInFlight?.catch(() => void 0);
|
|
9072
10069
|
let rotatorError;
|
|
9073
10070
|
try {
|
|
9074
10071
|
await this._receiveRotator?.dispose();
|
|
@@ -9143,6 +10140,15 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9143
10140
|
boot?.rotator,
|
|
9144
10141
|
boot?.provider
|
|
9145
10142
|
);
|
|
10143
|
+
wallet.refreshDeprecatedSigners(setup.info);
|
|
10144
|
+
{
|
|
10145
|
+
const ap = setup.arkProvider;
|
|
10146
|
+
if (typeof ap.onServerInfoChanged === "function") {
|
|
10147
|
+
wallet._serverInfoUnsub = ap.onServerInfoChanged((info) => {
|
|
10148
|
+
wallet._serverInfoInFlight = wallet._serverInfoInFlight.then(() => wallet.handleServerInfoChanged(info)).catch(() => void 0);
|
|
10149
|
+
});
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
9146
10152
|
if (boot?.provider) {
|
|
9147
10153
|
const resolvedBoarding = await resolveBoardingBootTapscript(
|
|
9148
10154
|
setup.contractRepository,
|
|
@@ -9175,7 +10181,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9175
10181
|
*/
|
|
9176
10182
|
async toReadonly() {
|
|
9177
10183
|
const readonlyIdentity = hasToReadonly(this.identity) ? await this.identity.toReadonly() : this.identity;
|
|
9178
|
-
|
|
10184
|
+
const readonly = new ReadonlyWallet(
|
|
9179
10185
|
readonlyIdentity,
|
|
9180
10186
|
this.network,
|
|
9181
10187
|
this.onchainProvider,
|
|
@@ -9190,6 +10196,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9190
10196
|
this.watcherConfig,
|
|
9191
10197
|
this.walletContractTimelocks
|
|
9192
10198
|
);
|
|
10199
|
+
readonly._deprecatedSigners = new Map(this._deprecatedSigners);
|
|
10200
|
+
return readonly;
|
|
9193
10201
|
}
|
|
9194
10202
|
/** Returns the delegate manager when delegation support is configured. */
|
|
9195
10203
|
async getDelegateManager() {
|
|
@@ -9215,10 +10223,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9215
10223
|
if (params.selectedVtxos && params.selectedVtxos.length > 0) {
|
|
9216
10224
|
return this._withTxLock(async () => {
|
|
9217
10225
|
const offchainTapscript = this.offchainTapscript;
|
|
9218
|
-
const
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
);
|
|
10226
|
+
const serverPubKey = this.arkServerPublicKey;
|
|
10227
|
+
const serverUnrollScript = this.serverUnrollScript;
|
|
10228
|
+
const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
|
|
9222
10229
|
const selectedVtxoSum = params.selectedVtxos.map((v) => v.value).reduce((a, b) => a + b, 0);
|
|
9223
10230
|
if (selectedVtxoSum < params.amount) {
|
|
9224
10231
|
throw new Error("Selected VTXOs do not cover specified amount");
|
|
@@ -9243,25 +10250,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9243
10250
|
amount: BigInt(selected.changeAmount)
|
|
9244
10251
|
});
|
|
9245
10252
|
}
|
|
9246
|
-
this.
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
arkTxid,
|
|
9255
|
-
signedCheckpointTxs,
|
|
9256
|
-
params.amount,
|
|
9257
|
-
selected.changeAmount,
|
|
9258
|
-
selected.changeAmount > 0n ? outputs.length - 1 : 0,
|
|
9259
|
-
offchainTapscript
|
|
9260
|
-
);
|
|
9261
|
-
return arkTxid;
|
|
9262
|
-
} finally {
|
|
9263
|
-
this._removePendingSpends(selected.inputs);
|
|
9264
|
-
}
|
|
10253
|
+
return this._submitOffchainSpend(selected.inputs, outputs, {
|
|
10254
|
+
sentAmount: params.amount,
|
|
10255
|
+
changeAmount: selected.changeAmount,
|
|
10256
|
+
changeVout: selected.changeAmount > 0n ? outputs.length - 1 : 0,
|
|
10257
|
+
offchainTapscript,
|
|
10258
|
+
serverPubKey,
|
|
10259
|
+
serverUnrollScript
|
|
10260
|
+
});
|
|
9265
10261
|
});
|
|
9266
10262
|
}
|
|
9267
10263
|
return this.send({
|
|
@@ -9291,8 +10287,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9291
10287
|
}
|
|
9292
10288
|
}
|
|
9293
10289
|
}
|
|
10290
|
+
const offchainAddress = await this.getAddress();
|
|
10291
|
+
const offchainPkScript = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress).pkScript;
|
|
10292
|
+
const offchainOutputScript = base.hex.encode(offchainPkScript);
|
|
9294
10293
|
if (!params) {
|
|
9295
|
-
const { fees } = await this.arkProvider.getInfo();
|
|
10294
|
+
const { fees, vtxoMaxAmount } = await this.arkProvider.getInfo();
|
|
9296
10295
|
const estimator = new Estimator(fees.intentFee);
|
|
9297
10296
|
let amount = 0;
|
|
9298
10297
|
const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
|
|
@@ -9334,20 +10333,31 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9334
10333
|
if (inputFee.satoshis >= vtxo.value) {
|
|
9335
10334
|
continue;
|
|
9336
10335
|
}
|
|
10336
|
+
const net = vtxo.value - inputFee.satoshis;
|
|
10337
|
+
if (vtxoMaxAmount >= 0n) {
|
|
10338
|
+
const projectedAmount = BigInt(amount + net);
|
|
10339
|
+
const projectedOutputFee = estimator.evalOffchainOutput({
|
|
10340
|
+
amount: projectedAmount,
|
|
10341
|
+
script: offchainOutputScript
|
|
10342
|
+
});
|
|
10343
|
+
if (projectedAmount - BigInt(projectedOutputFee.satoshis) > vtxoMaxAmount) {
|
|
10344
|
+
continue;
|
|
10345
|
+
}
|
|
10346
|
+
}
|
|
9337
10347
|
filteredVtxos.push(vtxo);
|
|
9338
|
-
amount +=
|
|
10348
|
+
amount += net;
|
|
9339
10349
|
}
|
|
9340
10350
|
const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
|
|
9341
10351
|
if (inputs.length === 0) {
|
|
9342
10352
|
throw new Error("No inputs found");
|
|
9343
10353
|
}
|
|
9344
10354
|
const output = {
|
|
9345
|
-
address:
|
|
10355
|
+
address: offchainAddress,
|
|
9346
10356
|
amount: BigInt(amount)
|
|
9347
10357
|
};
|
|
9348
10358
|
const outputFee = estimator.evalOffchainOutput({
|
|
9349
10359
|
amount: output.amount,
|
|
9350
|
-
script:
|
|
10360
|
+
script: offchainOutputScript
|
|
9351
10361
|
});
|
|
9352
10362
|
output.amount -= BigInt(outputFee.satoshis);
|
|
9353
10363
|
if (output.amount <= this.dustAmount) {
|
|
@@ -9387,8 +10397,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9387
10397
|
}
|
|
9388
10398
|
}
|
|
9389
10399
|
let outputAssets;
|
|
9390
|
-
const
|
|
9391
|
-
const assetOutputIndex = findDestinationOutputIndex(outputs, destinationScript);
|
|
10400
|
+
const assetOutputIndex = findDestinationOutputIndex(outputs, offchainPkScript);
|
|
9392
10401
|
if (assetInputs.size > 0) {
|
|
9393
10402
|
if (assetOutputIndex === -1) {
|
|
9394
10403
|
throw new Error("Cannot assign assets: no output matches the destination address");
|
|
@@ -9736,7 +10745,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9736
10745
|
try {
|
|
9737
10746
|
return await this.arkProvider.registerIntent(intent);
|
|
9738
10747
|
} catch (error) {
|
|
9739
|
-
if (error instanceof
|
|
10748
|
+
if (error instanceof chunkC6OODRWD_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
|
|
9740
10749
|
const deleteIntent = await this.makeDeleteIntentSignature(inputs);
|
|
9741
10750
|
await this.arkProvider.deleteIntent(deleteIntent);
|
|
9742
10751
|
return this.arkProvider.registerIntent(intent);
|
|
@@ -9752,7 +10761,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9752
10761
|
expire_at: 0,
|
|
9753
10762
|
cosigners_public_keys: cosignerPubKeys
|
|
9754
10763
|
};
|
|
9755
|
-
const proof =
|
|
10764
|
+
const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, outputs);
|
|
9756
10765
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9757
10766
|
return {
|
|
9758
10767
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9764,7 +10773,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9764
10773
|
type: "delete",
|
|
9765
10774
|
expire_at: 0
|
|
9766
10775
|
};
|
|
9767
|
-
const proof =
|
|
10776
|
+
const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, []);
|
|
9768
10777
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9769
10778
|
return {
|
|
9770
10779
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9776,7 +10785,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9776
10785
|
type: "get-pending-tx",
|
|
9777
10786
|
expire_at: 0
|
|
9778
10787
|
};
|
|
9779
|
-
const proof =
|
|
10788
|
+
const proof = chunkC6OODRWD_cjs.Intent.create(message, coins, []);
|
|
9780
10789
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9781
10790
|
return {
|
|
9782
10791
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9927,12 +10936,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9927
10936
|
throw new Error("At least one receiver is required");
|
|
9928
10937
|
}
|
|
9929
10938
|
const offchainTapscript = this.offchainTapscript;
|
|
9930
|
-
const
|
|
10939
|
+
const serverPubKey = this.arkServerPublicKey;
|
|
10940
|
+
const serverUnrollScript = this.serverUnrollScript;
|
|
10941
|
+
const outputAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
|
|
9931
10942
|
const address = outputAddress.encode();
|
|
9932
10943
|
const recipients = validateRecipients(args, Number(this.dustAmount));
|
|
9933
|
-
const
|
|
10944
|
+
const allVirtualCoins = await this.getVtxos({
|
|
9934
10945
|
withRecoverable: false
|
|
9935
10946
|
});
|
|
10947
|
+
const pendingRecovery = await this.pendingRecoveryOutpoints();
|
|
10948
|
+
const virtualCoins = pendingRecovery.size ? allVirtualCoins.filter((c) => !pendingRecovery.has(`${c.txid}:${c.vout}`)) : allVirtualCoins;
|
|
9936
10949
|
const assetChanges = /* @__PURE__ */ new Map();
|
|
9937
10950
|
let selectedCoins = [];
|
|
9938
10951
|
let btcAmountToSelect = 0;
|
|
@@ -10054,33 +11067,128 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
10054
11067
|
outputs.push(Extension.create([assetPacket]).txOut());
|
|
10055
11068
|
}
|
|
10056
11069
|
const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
10057
|
-
this.
|
|
11070
|
+
return this._submitOffchainSpend(selectedCoins, outputs, {
|
|
11071
|
+
sentAmount,
|
|
11072
|
+
changeAmount: BigInt(changeAmount),
|
|
11073
|
+
changeVout: changeReceiver ? changeIndex : 0,
|
|
11074
|
+
offchainTapscript,
|
|
11075
|
+
serverPubKey,
|
|
11076
|
+
serverUnrollScript,
|
|
11077
|
+
changeAssets: changeReceiver?.assets
|
|
11078
|
+
});
|
|
11079
|
+
}
|
|
11080
|
+
/**
|
|
11081
|
+
* Shared tail of every Ark-transaction spend path (`send`, selected-VTXO
|
|
11082
|
+
* `sendBitcoin`, and {@link sendSelectedVtxosToSelf}): hide the inputs from
|
|
11083
|
+
* concurrent `getVtxos()`, build+submit the offchain tx, persist the spent
|
|
11084
|
+
* inputs and any wallet-owned (change / self) output, then release the
|
|
11085
|
+
* pending-spend hold. Callers own coin selection, output construction, and
|
|
11086
|
+
* the synchronous epoch snapshot; this owns the submit/persist sequence.
|
|
11087
|
+
*/
|
|
11088
|
+
async _submitOffchainSpend(inputs, outputs, persist) {
|
|
11089
|
+
this._addPendingSpends(inputs);
|
|
10058
11090
|
try {
|
|
10059
11091
|
const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
|
|
10060
|
-
|
|
10061
|
-
outputs
|
|
11092
|
+
inputs,
|
|
11093
|
+
outputs,
|
|
11094
|
+
persist.serverUnrollScript
|
|
10062
11095
|
);
|
|
10063
11096
|
await this.updateDbAfterOffchainTx(
|
|
10064
|
-
|
|
11097
|
+
inputs,
|
|
10065
11098
|
arkTxid,
|
|
10066
11099
|
signedCheckpointTxs,
|
|
10067
|
-
sentAmount,
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
offchainTapscript,
|
|
10071
|
-
|
|
11100
|
+
persist.sentAmount,
|
|
11101
|
+
persist.changeAmount,
|
|
11102
|
+
persist.changeVout,
|
|
11103
|
+
persist.offchainTapscript,
|
|
11104
|
+
persist.serverPubKey,
|
|
11105
|
+
persist.changeAssets,
|
|
11106
|
+
persist.recordSentHistory ?? true
|
|
10072
11107
|
);
|
|
10073
11108
|
return arkTxid;
|
|
10074
11109
|
} finally {
|
|
10075
|
-
this._removePendingSpends(
|
|
10076
|
-
}
|
|
11110
|
+
this._removePendingSpends(inputs);
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
11113
|
+
/**
|
|
11114
|
+
* @internal Migration primitive (deprecated-signer plan, step 1). Spend an
|
|
11115
|
+
* explicit set of the wallet's own deprecated-signer VTXOs into a single
|
|
11116
|
+
* full-value output on the wallet's *active* signer, through the Ark send
|
|
11117
|
+
* path (not `settle`) so arkd builds checkpoints against the active server
|
|
11118
|
+
* epoch. Consumed in-process by {@link VtxoManager}'s migration pass; not
|
|
11119
|
+
* part of the public `IWallet` API and never accepts boarding `ExtendedCoin`
|
|
11120
|
+
* inputs.
|
|
11121
|
+
*
|
|
11122
|
+
* The caller (`migrateCore`) must have already moved the wallet onto the
|
|
11123
|
+
* active signer (`ensureReceiveOnActiveSigner`) and sized the batch (caps +
|
|
11124
|
+
* dust floor); this method validates the inputs, preserves all input assets
|
|
11125
|
+
* on the self output, and persists the new active-signer VTXO even though
|
|
11126
|
+
* there is no separate change output. It records no `TxSent` history — the
|
|
11127
|
+
* funds never leave the wallet.
|
|
11128
|
+
*/
|
|
11129
|
+
async sendSelectedVtxosToSelf(inputs) {
|
|
11130
|
+
if (inputs.length === 0) {
|
|
11131
|
+
throw new Error("sendSelectedVtxosToSelf: no inputs");
|
|
11132
|
+
}
|
|
11133
|
+
return this._withTxLock(async () => {
|
|
11134
|
+
const offchainTapscript = this.offchainTapscript;
|
|
11135
|
+
const serverPubKey = this.arkServerPublicKey;
|
|
11136
|
+
const serverUnrollScript = this.serverUnrollScript;
|
|
11137
|
+
const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
|
|
11138
|
+
for (const input of inputs) {
|
|
11139
|
+
if (!isSpendable(input) || isRecoverable(input)) {
|
|
11140
|
+
throw new Error(
|
|
11141
|
+
`sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} is not cooperatively spendable`
|
|
11142
|
+
);
|
|
11143
|
+
}
|
|
11144
|
+
if (!input.virtualStatus.batchExpiry) {
|
|
11145
|
+
throw new Error(
|
|
11146
|
+
`sendSelectedVtxosToSelf: input ${input.txid}:${input.vout} has no batchExpiry`
|
|
11147
|
+
);
|
|
11148
|
+
}
|
|
11149
|
+
}
|
|
11150
|
+
const total = inputs.reduce((sum, c) => sum + BigInt(c.value), 0n);
|
|
11151
|
+
const outputs = [
|
|
11152
|
+
{
|
|
11153
|
+
script: total < this.dustAmount ? arkAddress.subdustPkScript : arkAddress.pkScript,
|
|
11154
|
+
amount: total
|
|
11155
|
+
}
|
|
11156
|
+
];
|
|
11157
|
+
const assetInputs = selectedCoinsToAssetInputs(inputs);
|
|
11158
|
+
let selfAssets;
|
|
11159
|
+
if (assetInputs.size > 0) {
|
|
11160
|
+
const totals = /* @__PURE__ */ new Map();
|
|
11161
|
+
for (const [, assets] of assetInputs) {
|
|
11162
|
+
for (const a of assets) {
|
|
11163
|
+
totals.set(a.assetId, (totals.get(a.assetId) ?? 0n) + a.amount);
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
selfAssets = [...totals].map(([assetId, amount]) => ({ assetId, amount }));
|
|
11167
|
+
const selfReceiver = {
|
|
11168
|
+
address: arkAddress.encode(),
|
|
11169
|
+
assets: selfAssets
|
|
11170
|
+
};
|
|
11171
|
+
const packet = createAssetPacket(assetInputs, [], selfReceiver);
|
|
11172
|
+
outputs.push(Extension.create([packet]).txOut());
|
|
11173
|
+
}
|
|
11174
|
+
return this._submitOffchainSpend(inputs, outputs, {
|
|
11175
|
+
sentAmount: 0,
|
|
11176
|
+
changeAmount: total,
|
|
11177
|
+
changeVout: 0,
|
|
11178
|
+
offchainTapscript,
|
|
11179
|
+
serverPubKey,
|
|
11180
|
+
changeAssets: selfAssets,
|
|
11181
|
+
recordSentHistory: false,
|
|
11182
|
+
serverUnrollScript
|
|
11183
|
+
});
|
|
11184
|
+
});
|
|
10077
11185
|
}
|
|
10078
11186
|
/**
|
|
10079
11187
|
* Build an offchain transaction from the given inputs and outputs,
|
|
10080
11188
|
* sign it, submit to the Arkade provider, and finalize.
|
|
10081
11189
|
* @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
|
|
10082
11190
|
*/
|
|
10083
|
-
async buildAndSubmitOffchainTx(inputs, outputs) {
|
|
11191
|
+
async buildAndSubmitOffchainTx(inputs, outputs, serverUnrollScript = this.serverUnrollScript) {
|
|
10084
11192
|
const offchainTx = buildOffchainTx(
|
|
10085
11193
|
inputs.map((input) => {
|
|
10086
11194
|
return {
|
|
@@ -10089,7 +11197,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
10089
11197
|
};
|
|
10090
11198
|
}),
|
|
10091
11199
|
outputs,
|
|
10092
|
-
|
|
11200
|
+
serverUnrollScript
|
|
10093
11201
|
);
|
|
10094
11202
|
const arkTxJobs = inputs.map((input, index) => ({
|
|
10095
11203
|
index,
|
|
@@ -10163,14 +11271,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
10163
11271
|
return { arkTxid, signedCheckpointTxs };
|
|
10164
11272
|
}
|
|
10165
11273
|
// mark virtual outputs as spent, save change outputs if any.
|
|
10166
|
-
// `offchainTapscript`
|
|
10167
|
-
// `_txLock` before any `await`; deriving both the
|
|
10168
|
-
// metadata and `primaryAddress` from
|
|
10169
|
-
// record matches the pkScript the server saw on the inbound
|
|
10170
|
-
// transaction, even if `
|
|
10171
|
-
// `this.
|
|
10172
|
-
async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, changeAssets) {
|
|
10173
|
-
const primaryAddress = offchainTapscript.address(this.network.hrp,
|
|
11274
|
+
// `offchainTapscript` and `serverPubKey` are the epoch snapshot the
|
|
11275
|
+
// caller captured under `_txLock` before any `await`; deriving both the
|
|
11276
|
+
// change-VTXO metadata and `primaryAddress` from them here guarantees the
|
|
11277
|
+
// local record matches the address/pkScript the server saw on the inbound
|
|
11278
|
+
// transaction, even if `rotateServerSigner` swaps `this.offchainTapscript`
|
|
11279
|
+
// / `this.arkServerPublicKey` mid-flight.
|
|
11280
|
+
async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, serverPubKey, changeAssets, recordSentHistory = true) {
|
|
11281
|
+
const primaryAddress = offchainTapscript.address(this.network.hrp, serverPubKey).encode();
|
|
10174
11282
|
try {
|
|
10175
11283
|
const spentVtxos = [];
|
|
10176
11284
|
const commitmentTxIds = /* @__PURE__ */ new Set();
|
|
@@ -10277,19 +11385,21 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
10277
11385
|
[changeVtxo]
|
|
10278
11386
|
);
|
|
10279
11387
|
}
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
11388
|
+
if (recordSentHistory) {
|
|
11389
|
+
await this.walletRepository.saveTransactions(primaryAddress, [
|
|
11390
|
+
{
|
|
11391
|
+
key: {
|
|
11392
|
+
boardingTxid: "",
|
|
11393
|
+
commitmentTxid: "",
|
|
11394
|
+
arkTxid
|
|
11395
|
+
},
|
|
11396
|
+
amount: sentAmount,
|
|
11397
|
+
type: "SENT" /* TxSent */,
|
|
11398
|
+
settled: false,
|
|
11399
|
+
createdAt
|
|
11400
|
+
}
|
|
11401
|
+
]);
|
|
11402
|
+
}
|
|
10293
11403
|
} catch (e) {
|
|
10294
11404
|
console.warn("error saving offchain tx to repository", e);
|
|
10295
11405
|
throw e;
|
|
@@ -10580,7 +11690,7 @@ var MessageBus = class {
|
|
|
10580
11690
|
this.initialized = true;
|
|
10581
11691
|
}
|
|
10582
11692
|
async buildServices(config) {
|
|
10583
|
-
const arkProvider = new
|
|
11693
|
+
const arkProvider = new chunkC6OODRWD_cjs.RestArkProvider(config.arkServer.url);
|
|
10584
11694
|
const storage = {
|
|
10585
11695
|
walletRepository: this.walletRepository,
|
|
10586
11696
|
contractRepository: this.contractRepository
|
|
@@ -11147,6 +12257,82 @@ var DelegateNotConfiguredError = class extends Error {
|
|
|
11147
12257
|
};
|
|
11148
12258
|
var DelegatorNotConfiguredError = DelegateNotConfiguredError;
|
|
11149
12259
|
var DEFAULT_MESSAGE_TAG = "WALLET_UPDATER";
|
|
12260
|
+
var serializeMigrationVtxoRef = (ref) => ({
|
|
12261
|
+
txid: ref.txid,
|
|
12262
|
+
vout: ref.vout,
|
|
12263
|
+
value: ref.value,
|
|
12264
|
+
signerPubKey: ref.signerPubKey,
|
|
12265
|
+
cutoffDate: ref.cutoffDate?.toString()
|
|
12266
|
+
});
|
|
12267
|
+
var deserializeMigrationVtxoRef = (ref) => ({
|
|
12268
|
+
txid: ref.txid,
|
|
12269
|
+
vout: ref.vout,
|
|
12270
|
+
value: ref.value,
|
|
12271
|
+
signerPubKey: ref.signerPubKey,
|
|
12272
|
+
cutoffDate: ref.cutoffDate != null ? BigInt(ref.cutoffDate) : void 0
|
|
12273
|
+
});
|
|
12274
|
+
var serializeDeprecatedSignerReport = (report) => ({
|
|
12275
|
+
signerPubKey: report.signerPubKey,
|
|
12276
|
+
status: report.status,
|
|
12277
|
+
cutoffDate: report.cutoffDate?.toString(),
|
|
12278
|
+
secondsUntilCutoff: report.secondsUntilCutoff,
|
|
12279
|
+
vtxoCount: report.vtxoCount,
|
|
12280
|
+
totalValue: report.totalValue,
|
|
12281
|
+
boardingCount: report.boardingCount,
|
|
12282
|
+
boardingValue: report.boardingValue,
|
|
12283
|
+
recoverableCount: report.recoverableCount,
|
|
12284
|
+
recoverableValue: report.recoverableValue,
|
|
12285
|
+
awaitingSweepCount: report.awaitingSweepCount,
|
|
12286
|
+
awaitingSweepValue: report.awaitingSweepValue,
|
|
12287
|
+
nextSweepEta: report.nextSweepEta
|
|
12288
|
+
});
|
|
12289
|
+
var deserializeDeprecatedSignerReport = (report) => ({
|
|
12290
|
+
signerPubKey: report.signerPubKey,
|
|
12291
|
+
status: report.status,
|
|
12292
|
+
cutoffDate: report.cutoffDate != null ? BigInt(report.cutoffDate) : void 0,
|
|
12293
|
+
secondsUntilCutoff: report.secondsUntilCutoff,
|
|
12294
|
+
vtxoCount: report.vtxoCount,
|
|
12295
|
+
totalValue: report.totalValue,
|
|
12296
|
+
boardingCount: report.boardingCount,
|
|
12297
|
+
boardingValue: report.boardingValue,
|
|
12298
|
+
recoverableCount: report.recoverableCount,
|
|
12299
|
+
recoverableValue: report.recoverableValue,
|
|
12300
|
+
awaitingSweepCount: report.awaitingSweepCount,
|
|
12301
|
+
awaitingSweepValue: report.awaitingSweepValue,
|
|
12302
|
+
nextSweepEta: report.nextSweepEta
|
|
12303
|
+
});
|
|
12304
|
+
var serializeMigrationLegReport = (leg) => ({
|
|
12305
|
+
txid: leg.txid,
|
|
12306
|
+
migrated: leg.migrated.map(serializeMigrationVtxoRef),
|
|
12307
|
+
skipped: leg.skipped,
|
|
12308
|
+
deferred: leg.deferred,
|
|
12309
|
+
oversized: leg.oversized?.map(serializeMigrationVtxoRef),
|
|
12310
|
+
error: leg.error
|
|
12311
|
+
});
|
|
12312
|
+
var deserializeMigrationLegReport = (leg) => ({
|
|
12313
|
+
txid: leg.txid,
|
|
12314
|
+
migrated: leg.migrated.map(deserializeMigrationVtxoRef),
|
|
12315
|
+
skipped: leg.skipped,
|
|
12316
|
+
deferred: leg.deferred,
|
|
12317
|
+
oversized: leg.oversized?.map(deserializeMigrationVtxoRef),
|
|
12318
|
+
error: leg.error
|
|
12319
|
+
});
|
|
12320
|
+
var serializeMigrationReport = (report) => ({
|
|
12321
|
+
rotated: report.rotated,
|
|
12322
|
+
skipped: report.skipped,
|
|
12323
|
+
vtxos: report.vtxos ? serializeMigrationLegReport(report.vtxos) : void 0,
|
|
12324
|
+
boarding: report.boarding ? serializeMigrationLegReport(report.boarding) : void 0,
|
|
12325
|
+
expired: report.expired.map(serializeMigrationVtxoRef),
|
|
12326
|
+
signers: report.signers.map(serializeDeprecatedSignerReport)
|
|
12327
|
+
});
|
|
12328
|
+
var deserializeMigrationReport = (report) => ({
|
|
12329
|
+
rotated: report.rotated,
|
|
12330
|
+
skipped: report.skipped,
|
|
12331
|
+
vtxos: report.vtxos ? deserializeMigrationLegReport(report.vtxos) : void 0,
|
|
12332
|
+
boarding: report.boarding ? deserializeMigrationLegReport(report.boarding) : void 0,
|
|
12333
|
+
expired: report.expired.map(deserializeMigrationVtxoRef),
|
|
12334
|
+
signers: report.signers.map(deserializeDeprecatedSignerReport)
|
|
12335
|
+
});
|
|
11150
12336
|
var WalletMessageHandler = class {
|
|
11151
12337
|
messageTag;
|
|
11152
12338
|
wallet;
|
|
@@ -11228,7 +12414,9 @@ var WalletMessageHandler = class {
|
|
|
11228
12414
|
// page-side PING / MESSAGE_BUS_NOT_INITIALIZED path triggered by concurrent
|
|
11229
12415
|
// short requests (GET_STATUS, GET_BALANCE, ...).
|
|
11230
12416
|
isLongRunning(message) {
|
|
11231
|
-
return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || //
|
|
12417
|
+
return message.type === "SETTLE" || message.type === "RECOVER_VTXOS" || message.type === "RENEW_VTXOS" || // Migration may apply a server-signer rotation and then run a full
|
|
12418
|
+
// settle, so it streams settlement events like RENEW_VTXOS.
|
|
12419
|
+
message.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS" || // HD restore walks the index range with one indexer round-trip per
|
|
11232
12420
|
// step until it hits gapLimit consecutive unused indices. The bus
|
|
11233
12421
|
// deadline must not race the scan; liveness stays covered by PING.
|
|
11234
12422
|
message.type === "RESTORE_WALLET";
|
|
@@ -11594,6 +12782,36 @@ var WalletMessageHandler = class {
|
|
|
11594
12782
|
payload: { txid }
|
|
11595
12783
|
});
|
|
11596
12784
|
}
|
|
12785
|
+
case "MIGRATE_DEPRECATED_SIGNER_VTXOS": {
|
|
12786
|
+
const wallet = this.requireWallet();
|
|
12787
|
+
const vtxoManager = await wallet.getVtxoManager();
|
|
12788
|
+
const report = await vtxoManager.migrateDeprecatedSignerVtxos({
|
|
12789
|
+
eventCallback: (e) => {
|
|
12790
|
+
this.scheduleForNextTick(
|
|
12791
|
+
() => this.tagged({
|
|
12792
|
+
id,
|
|
12793
|
+
type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_EVENT",
|
|
12794
|
+
payload: e
|
|
12795
|
+
})
|
|
12796
|
+
);
|
|
12797
|
+
}
|
|
12798
|
+
});
|
|
12799
|
+
return this.tagged({
|
|
12800
|
+
id,
|
|
12801
|
+
type: "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS",
|
|
12802
|
+
payload: { report: serializeMigrationReport(report) }
|
|
12803
|
+
});
|
|
12804
|
+
}
|
|
12805
|
+
case "GET_DEPRECATED_SIGNER_STATUS": {
|
|
12806
|
+
const wallet = this.requireWallet();
|
|
12807
|
+
const vtxoManager = await wallet.getVtxoManager();
|
|
12808
|
+
const signers = await vtxoManager.getDeprecatedSignerStatus();
|
|
12809
|
+
return this.tagged({
|
|
12810
|
+
id,
|
|
12811
|
+
type: "DEPRECATED_SIGNER_STATUS",
|
|
12812
|
+
payload: { signers: signers.map(serializeDeprecatedSignerReport) }
|
|
12813
|
+
});
|
|
12814
|
+
}
|
|
11597
12815
|
case "RESTORE_WALLET": {
|
|
11598
12816
|
const wallet = this.requireWallet();
|
|
11599
12817
|
try {
|
|
@@ -11623,13 +12841,14 @@ var WalletMessageHandler = class {
|
|
|
11623
12841
|
// Wallet methods
|
|
11624
12842
|
async handleInitWallet({ payload }) {
|
|
11625
12843
|
const { arkServerUrl } = payload;
|
|
11626
|
-
this.indexerProvider = new
|
|
12844
|
+
this.indexerProvider = new chunkC6OODRWD_cjs.RestIndexerProvider(arkServerUrl);
|
|
11627
12845
|
await this.onWalletInitialized();
|
|
11628
12846
|
}
|
|
11629
12847
|
async handleGetBalance() {
|
|
11630
|
-
const [boardingUtxos, allVtxos] = await Promise.all([
|
|
12848
|
+
const [boardingUtxos, allVtxos, pendingOutpoints] = await Promise.all([
|
|
11631
12849
|
this.getAllBoardingUtxos(),
|
|
11632
|
-
this.getVtxosFromRepo()
|
|
12850
|
+
this.getVtxosFromRepo(),
|
|
12851
|
+
this.readonlyWallet ? this.readonlyWallet.pendingRecoveryOutpoints() : Promise.resolve(/* @__PURE__ */ new Set())
|
|
11633
12852
|
]);
|
|
11634
12853
|
let confirmed = 0;
|
|
11635
12854
|
let unconfirmed = 0;
|
|
@@ -11645,8 +12864,11 @@ var WalletMessageHandler = class {
|
|
|
11645
12864
|
let settled = 0;
|
|
11646
12865
|
let preconfirmed = 0;
|
|
11647
12866
|
let recoverable = 0;
|
|
12867
|
+
let pendingRecovery = 0;
|
|
11648
12868
|
for (const vtxo of spendableVtxos) {
|
|
11649
|
-
if (vtxo.
|
|
12869
|
+
if (pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
|
|
12870
|
+
pendingRecovery += vtxo.value;
|
|
12871
|
+
} else if (vtxo.virtualStatus.state === "settled") {
|
|
11650
12872
|
settled += vtxo.value;
|
|
11651
12873
|
} else if (vtxo.virtualStatus.state === "preconfirmed") {
|
|
11652
12874
|
preconfirmed += vtxo.value;
|
|
@@ -11658,7 +12880,7 @@ var WalletMessageHandler = class {
|
|
|
11658
12880
|
}
|
|
11659
12881
|
}
|
|
11660
12882
|
const totalBoarding = confirmed + unconfirmed;
|
|
11661
|
-
const totalOffchain = settled + preconfirmed + recoverable;
|
|
12883
|
+
const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
|
|
11662
12884
|
const assetBalances = /* @__PURE__ */ new Map();
|
|
11663
12885
|
for (const vtxo of spendableVtxos) {
|
|
11664
12886
|
if (vtxo.assets) {
|
|
@@ -11682,6 +12904,7 @@ var WalletMessageHandler = class {
|
|
|
11682
12904
|
preconfirmed,
|
|
11683
12905
|
available: settled + preconfirmed,
|
|
11684
12906
|
recoverable,
|
|
12907
|
+
pendingRecovery,
|
|
11685
12908
|
total: totalBoarding + totalOffchain,
|
|
11686
12909
|
assets
|
|
11687
12910
|
};
|
|
@@ -12067,6 +13290,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
|
|
|
12067
13290
|
GET_EXPIRING_VTXOS: 2e4,
|
|
12068
13291
|
GET_EXPIRED_BOARDING_UTXOS: 2e4,
|
|
12069
13292
|
GET_RECOVERABLE_BALANCE: 2e4,
|
|
13293
|
+
GET_DEPRECATED_SIGNER_STATUS: 2e4,
|
|
12070
13294
|
RELOAD_WALLET: 2e4,
|
|
12071
13295
|
// Transactions — need more headroom.
|
|
12072
13296
|
// SETTLE / RECOVER_VTXOS / RENEW_VTXOS go through the streaming path and
|
|
@@ -12082,6 +13306,9 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
|
|
|
12082
13306
|
RECOVER_VTXOS: 5e4,
|
|
12083
13307
|
RENEW_VTXOS: 5e4,
|
|
12084
13308
|
SWEEP_EXPIRED_BOARDING_UTXOS: 5e4,
|
|
13309
|
+
// Streaming/long-running like RENEW_VTXOS (rotation + settle); the value is
|
|
13310
|
+
// kept for type completeness and is never enforced as an inactivity deadline.
|
|
13311
|
+
MIGRATE_DEPRECATED_SIGNER_VTXOS: 5e4,
|
|
12085
13312
|
// RESTORE_WALLET is a streaming/long-running path (sendMessageWithEvents)
|
|
12086
13313
|
// like SETTLE; the value here is kept for type completeness and is never
|
|
12087
13314
|
// enforced as an inactivity deadline.
|
|
@@ -12107,6 +13334,7 @@ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
|
12107
13334
|
"GET_DELEGATE_INFO",
|
|
12108
13335
|
"GET_RECOVERABLE_BALANCE",
|
|
12109
13336
|
"GET_EXPIRED_BOARDING_UTXOS",
|
|
13337
|
+
"GET_DEPRECATED_SIGNER_STATUS",
|
|
12110
13338
|
"GET_VTXOS",
|
|
12111
13339
|
"GET_CONTRACTS",
|
|
12112
13340
|
"GET_CONTRACTS_WITH_VTXOS",
|
|
@@ -13229,6 +14457,42 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
|
|
|
13229
14457
|
throw new Error(`Failed to sweep expired boarding utxos: ${e}`);
|
|
13230
14458
|
}
|
|
13231
14459
|
},
|
|
14460
|
+
async migrateDeprecatedSignerVtxos(options) {
|
|
14461
|
+
const message = {
|
|
14462
|
+
tag: messageTag,
|
|
14463
|
+
type: "MIGRATE_DEPRECATED_SIGNER_VTXOS",
|
|
14464
|
+
id: getRandomId()
|
|
14465
|
+
};
|
|
14466
|
+
try {
|
|
14467
|
+
const response = await wallet.sendMessageWithEvents(
|
|
14468
|
+
message,
|
|
14469
|
+
(resp) => options?.eventCallback?.(
|
|
14470
|
+
resp.payload
|
|
14471
|
+
),
|
|
14472
|
+
(resp) => resp.type === "MIGRATE_DEPRECATED_SIGNER_VTXOS_SUCCESS"
|
|
14473
|
+
);
|
|
14474
|
+
return deserializeMigrationReport(
|
|
14475
|
+
response.payload.report
|
|
14476
|
+
);
|
|
14477
|
+
} catch (e) {
|
|
14478
|
+
throw new Error(`Failed to migrate deprecated-signer vtxos: ${e}`);
|
|
14479
|
+
}
|
|
14480
|
+
},
|
|
14481
|
+
async getDeprecatedSignerStatus() {
|
|
14482
|
+
const message = {
|
|
14483
|
+
tag: messageTag,
|
|
14484
|
+
type: "GET_DEPRECATED_SIGNER_STATUS",
|
|
14485
|
+
id: getRandomId()
|
|
14486
|
+
};
|
|
14487
|
+
try {
|
|
14488
|
+
const response = await wallet.sendMessage(message);
|
|
14489
|
+
return response.payload.signers.map(
|
|
14490
|
+
deserializeDeprecatedSignerReport
|
|
14491
|
+
);
|
|
14492
|
+
} catch (e) {
|
|
14493
|
+
throw new Error(`Failed to get deprecated-signer status: ${e}`);
|
|
14494
|
+
}
|
|
14495
|
+
},
|
|
13232
14496
|
async dispose() {
|
|
13233
14497
|
return;
|
|
13234
14498
|
}
|
|
@@ -13368,7 +14632,7 @@ var OnchainWallet = class _OnchainWallet {
|
|
|
13368
14632
|
if (!inputs) {
|
|
13369
14633
|
throw new Error("Fee estimation failed");
|
|
13370
14634
|
}
|
|
13371
|
-
let tx = new
|
|
14635
|
+
let tx = new chunkC6OODRWD_cjs.Transaction();
|
|
13372
14636
|
for (const input of inputs) {
|
|
13373
14637
|
tx.addInput({
|
|
13374
14638
|
txid: input.txid,
|
|
@@ -13399,7 +14663,7 @@ var OnchainWallet = class _OnchainWallet {
|
|
|
13399
14663
|
*/
|
|
13400
14664
|
async bumpP2A(parent) {
|
|
13401
14665
|
const parentVsize = parent.vsize;
|
|
13402
|
-
let child = new
|
|
14666
|
+
let child = new chunkC6OODRWD_cjs.Transaction({
|
|
13403
14667
|
version: 3,
|
|
13404
14668
|
allowLegacyWitnessUtxo: true
|
|
13405
14669
|
});
|
|
@@ -14134,13 +15398,19 @@ function isHeaderSubscribeResult(v) {
|
|
|
14134
15398
|
const obj = v;
|
|
14135
15399
|
return typeof obj.height === "number" && typeof obj.hex === "string";
|
|
14136
15400
|
}
|
|
15401
|
+
function errorText(err) {
|
|
15402
|
+
if (typeof err === "string") return err;
|
|
15403
|
+
if (err && typeof err === "object") {
|
|
15404
|
+
const e = err;
|
|
15405
|
+
return [e.message, e.str].filter((v) => typeof v === "string").join(" ");
|
|
15406
|
+
}
|
|
15407
|
+
return "";
|
|
15408
|
+
}
|
|
14137
15409
|
function isMissingHeightError(err) {
|
|
14138
|
-
|
|
14139
|
-
return msg.toLowerCase().includes("missingheight");
|
|
15410
|
+
return errorText(err).toLowerCase().includes("missingheight");
|
|
14140
15411
|
}
|
|
14141
15412
|
function isTxNotInBlockError(err) {
|
|
14142
|
-
const
|
|
14143
|
-
const normalized = msg.toLowerCase();
|
|
15413
|
+
const normalized = errorText(err).toLowerCase();
|
|
14144
15414
|
return normalized.includes("not yet in a block") || normalized.includes("not in a block") || normalized.includes("not in block") || normalized.includes("no confirmed transaction");
|
|
14145
15415
|
}
|
|
14146
15416
|
function childTxidFromHex(txHex) {
|
|
@@ -14156,7 +15426,7 @@ exports.BIP322 = void 0;
|
|
|
14156
15426
|
async function sign2(message, identity, network) {
|
|
14157
15427
|
const xOnlyPubKey = await identity.xOnlyPublicKey();
|
|
14158
15428
|
const payment = btcSigner.p2tr(xOnlyPubKey, void 0, network);
|
|
14159
|
-
const toSpend =
|
|
15429
|
+
const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
|
|
14160
15430
|
const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
|
|
14161
15431
|
const signed = await identity.sign(toSign, [0]);
|
|
14162
15432
|
signed.finalizeIdx(0);
|
|
@@ -14214,7 +15484,7 @@ function verifyP2TR(message, witnessItems, pkScript, pubkey) {
|
|
|
14214
15484
|
if (sighashType !== btcSigner.SigHash.DEFAULT && sighashType !== btcSigner.SigHash.ALL) {
|
|
14215
15485
|
return false;
|
|
14216
15486
|
}
|
|
14217
|
-
const toSpend =
|
|
15487
|
+
const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
14218
15488
|
const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
|
|
14219
15489
|
const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
|
|
14220
15490
|
const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
|
|
@@ -14235,7 +15505,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
|
|
|
14235
15505
|
}
|
|
14236
15506
|
const sighashType = sigWithHash[sigWithHash.length - 1];
|
|
14237
15507
|
const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
|
|
14238
|
-
const toSpend =
|
|
15508
|
+
const toSpend = chunkC6OODRWD_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
14239
15509
|
const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
|
|
14240
15510
|
const scriptCode = btcSigner.OutScript.encode({ type: "pkh", hash: addressHash });
|
|
14241
15511
|
const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
|
|
@@ -14288,7 +15558,7 @@ function encodeCompactSize(n) {
|
|
|
14288
15558
|
return buf;
|
|
14289
15559
|
}
|
|
14290
15560
|
function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
|
|
14291
|
-
const tx = new
|
|
15561
|
+
const tx = new chunkC6OODRWD_cjs.Transaction({ version: 0 });
|
|
14292
15562
|
tx.addInput({
|
|
14293
15563
|
txid: toSpend.id,
|
|
14294
15564
|
index: 0,
|
|
@@ -14302,12 +15572,12 @@ function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
|
|
|
14302
15572
|
});
|
|
14303
15573
|
tx.addOutput({
|
|
14304
15574
|
amount: 0n,
|
|
14305
|
-
script:
|
|
15575
|
+
script: chunkC6OODRWD_cjs.OP_RETURN_EMPTY_PKSCRIPT
|
|
14306
15576
|
});
|
|
14307
15577
|
return tx;
|
|
14308
15578
|
}
|
|
14309
15579
|
function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
14310
|
-
const tx = new
|
|
15580
|
+
const tx = new chunkC6OODRWD_cjs.Transaction({ version: 0 });
|
|
14311
15581
|
tx.addInput({
|
|
14312
15582
|
txid: toSpend.id,
|
|
14313
15583
|
index: 0,
|
|
@@ -14319,7 +15589,7 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
|
14319
15589
|
});
|
|
14320
15590
|
tx.addOutput({
|
|
14321
15591
|
amount: 0n,
|
|
14322
|
-
script:
|
|
15592
|
+
script: chunkC6OODRWD_cjs.OP_RETURN_EMPTY_PKSCRIPT
|
|
14323
15593
|
});
|
|
14324
15594
|
return tx;
|
|
14325
15595
|
}
|
|
@@ -14380,7 +15650,7 @@ exports.Unroll = void 0;
|
|
|
14380
15650
|
if (virtualTxs.txs.length === 0) {
|
|
14381
15651
|
throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
|
|
14382
15652
|
}
|
|
14383
|
-
const tx =
|
|
15653
|
+
const tx = chunkC6OODRWD_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
|
|
14384
15654
|
if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
|
|
14385
15655
|
const input = tx.getInput(0);
|
|
14386
15656
|
if (!input) {
|
|
@@ -14480,7 +15750,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
|
|
|
14480
15750
|
btcSigner.TaprootControlBlock.encode(spendingLeaf[0]).length
|
|
14481
15751
|
);
|
|
14482
15752
|
}
|
|
14483
|
-
const tx = new
|
|
15753
|
+
const tx = new chunkC6OODRWD_cjs.Transaction({ version: 2 });
|
|
14484
15754
|
for (const input of inputs) {
|
|
14485
15755
|
tx.addInput(input);
|
|
14486
15756
|
}
|
|
@@ -14656,6 +15926,8 @@ exports.WalletRepositoryImpl = WalletRepositoryImpl;
|
|
|
14656
15926
|
exports.WsElectrumChainSource = WsElectrumChainSource;
|
|
14657
15927
|
exports.buildForfeitTx = buildForfeitTx;
|
|
14658
15928
|
exports.buildOffchainTx = buildOffchainTx;
|
|
15929
|
+
exports.classifyAgainstSignerSet = classifyAgainstSignerSet;
|
|
15930
|
+
exports.classifyContractSigner = classifyContractSigner;
|
|
14659
15931
|
exports.closeDatabase = closeDatabase;
|
|
14660
15932
|
exports.combineTapscriptSigs = combineTapscriptSigs;
|
|
14661
15933
|
exports.contractFromArkContract = contractFromArkContract;
|
|
@@ -14671,6 +15943,7 @@ exports.getRandomId = getRandomId;
|
|
|
14671
15943
|
exports.hasBoardingTxExpired = hasBoardingTxExpired;
|
|
14672
15944
|
exports.isArkContract = isArkContract;
|
|
14673
15945
|
exports.isBatchSignable = isBatchSignable;
|
|
15946
|
+
exports.isCooperativelyMigratable = isCooperativelyMigratable;
|
|
14674
15947
|
exports.isDiscoverable = isDiscoverable;
|
|
14675
15948
|
exports.isExpired = isExpired;
|
|
14676
15949
|
exports.isRecoverable = isRecoverable;
|
|
@@ -14689,10 +15962,12 @@ exports.serializeAssets = serializeAssets;
|
|
|
14689
15962
|
exports.serializeUtxo = serializeUtxo;
|
|
14690
15963
|
exports.serializeVtxo = serializeVtxo;
|
|
14691
15964
|
exports.setupServiceWorker = setupServiceWorker;
|
|
15965
|
+
exports.signerSetFromInfo = signerSetFromInfo;
|
|
15966
|
+
exports.toXOnlySignerHex = toXOnlySignerHex;
|
|
14692
15967
|
exports.validateConnectorsTxGraph = validateConnectorsTxGraph;
|
|
14693
15968
|
exports.validateVtxoTxGraph = validateVtxoTxGraph;
|
|
14694
15969
|
exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
|
|
14695
15970
|
exports.waitForIncomingFunds = waitForIncomingFunds;
|
|
14696
15971
|
exports.warnAndFilterVtxosForScript = warnAndFilterVtxosForScript;
|
|
14697
|
-
//# sourceMappingURL=chunk-
|
|
14698
|
-
//# sourceMappingURL=chunk-
|
|
15972
|
+
//# sourceMappingURL=chunk-TUSGEWOX.cjs.map
|
|
15973
|
+
//# sourceMappingURL=chunk-TUSGEWOX.cjs.map
|