@arkade-os/sdk 0.4.33 → 0.4.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapters/expo.cjs +5 -5
- package/dist/adapters/expo.d.cts +2 -2
- package/dist/adapters/expo.d.ts +2 -2
- package/dist/adapters/expo.js +3 -3
- package/dist/adapters/indexedDB.cjs +5 -5
- package/dist/adapters/indexedDB.js +4 -4
- package/dist/{ark-DEsDMYGv.d.cts → ark-D6sau_6-.d.cts} +522 -9
- package/dist/{ark-DEsDMYGv.d.ts → ark-D6sau_6-.d.ts} +522 -9
- package/dist/{asyncStorageTaskQueue-D8T1VXEx.d.cts → asyncStorageTaskQueue-CpC027t_.d.cts} +2 -2
- package/dist/{asyncStorageTaskQueue-CMrTYlKG.d.ts → asyncStorageTaskQueue-GT8fmPUG.d.ts} +2 -2
- package/dist/{chunk-E22HEKLN.js → chunk-3JR77WQ4.js} +140 -42
- package/dist/chunk-3JR77WQ4.js.map +1 -0
- package/dist/{chunk-WMIPYZSB.cjs → chunk-CMPJR3HS.cjs} +42 -9
- package/dist/chunk-CMPJR3HS.cjs.map +1 -0
- package/dist/{chunk-AOJUURHM.js → chunk-CUSABEUQ.js} +141 -37
- package/dist/chunk-CUSABEUQ.js.map +1 -0
- package/dist/{chunk-HAVA4XB7.cjs → chunk-FM7T7JVL.cjs} +7 -7
- package/dist/{chunk-HAVA4XB7.cjs.map → chunk-FM7T7JVL.cjs.map} +1 -1
- package/dist/{chunk-GYSK5R57.cjs → chunk-GUTKJMSF.cjs} +164 -59
- package/dist/chunk-GUTKJMSF.cjs.map +1 -0
- package/dist/{chunk-7K3ROJF6.cjs → chunk-H2LX2KKY.cjs} +2161 -466
- package/dist/chunk-H2LX2KKY.cjs.map +1 -0
- package/dist/{chunk-DSS2GQUG.js → chunk-NOR7XOKN.js} +2021 -331
- package/dist/chunk-NOR7XOKN.js.map +1 -0
- package/dist/{chunk-BU3BU6XK.js → chunk-OURFR4UR.js} +3 -3
- package/dist/{chunk-BU3BU6XK.js.map → chunk-OURFR4UR.js.map} +1 -1
- package/dist/{chunk-TU3LVAPX.js → chunk-OUVTG72A.js} +43 -11
- package/dist/chunk-OUVTG72A.js.map +1 -0
- package/dist/{chunk-5CCRRL5S.cjs → chunk-VYS3KGRI.cjs} +19 -13
- package/dist/chunk-VYS3KGRI.cjs.map +1 -0
- package/dist/{chunk-SPDNHPM4.cjs → chunk-X2EQLK4O.cjs} +149 -46
- package/dist/chunk-X2EQLK4O.cjs.map +1 -0
- package/dist/{chunk-L6ZETTX3.js → chunk-XQS2HW4Q.js} +11 -5
- package/dist/chunk-XQS2HW4Q.js.map +1 -0
- package/dist/contracts/handlers/index.cjs +7 -7
- package/dist/contracts/handlers/index.d.cts +3 -3
- package/dist/contracts/handlers/index.d.ts +3 -3
- package/dist/contracts/handlers/index.js +2 -2
- package/dist/{delegate-BJeBNP5a.d.cts → delegate-C-L6gSZx.d.cts} +1 -1
- package/dist/{delegate-EXN2mfkb.d.ts → delegate-De5__fpZ.d.ts} +1 -1
- package/dist/{index-BG2ooYKO.d.ts → index-BETdjE_o.d.ts} +22 -16
- package/dist/{index-DHjEeHEp.d.cts → index-jwQfHP6D.d.cts} +22 -16
- package/dist/index.cjs +158 -130
- package/dist/index.d.cts +125 -16
- package/dist/index.d.ts +125 -16
- package/dist/index.js +4 -4
- package/dist/repositories/realm/index.cjs +14 -14
- 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 +5 -5
- package/dist/repositories/realm/index.js.map +1 -1
- package/dist/repositories/sqlite/index.cjs +13 -13
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.js +4 -4
- package/dist/{taskRunner-pIGyarFG.d.cts → taskRunner-DCyp6Gea.d.cts} +2 -2
- package/dist/{taskRunner-B7lBU45X.d.ts → taskRunner-DnxtObeq.d.ts} +2 -2
- package/dist/wallet/expo/background.cjs +14 -14
- package/dist/wallet/expo/background.d.cts +3 -3
- package/dist/wallet/expo/background.d.ts +3 -3
- package/dist/wallet/expo/background.js +6 -6
- package/dist/wallet/expo/index.cjs +13 -13
- package/dist/wallet/expo/index.d.cts +5 -5
- package/dist/wallet/expo/index.d.ts +5 -5
- package/dist/wallet/expo/index.js +5 -5
- package/dist/{wallet-D4Dll5Gu.d.cts → wallet-BWHbd5b1.d.cts} +388 -10
- package/dist/{wallet-C4L_X0i6.d.ts → wallet-Bth5uucA.d.ts} +388 -10
- package/dist/worker/expo/index.cjs +9 -9
- package/dist/worker/expo/index.d.cts +4 -4
- package/dist/worker/expo/index.d.ts +4 -4
- package/dist/worker/expo/index.js +5 -5
- package/package.json +5 -5
- package/dist/chunk-5CCRRL5S.cjs.map +0 -1
- package/dist/chunk-7K3ROJF6.cjs.map +0 -1
- package/dist/chunk-AOJUURHM.js.map +0 -1
- package/dist/chunk-DSS2GQUG.js.map +0 -1
- package/dist/chunk-E22HEKLN.js.map +0 -1
- package/dist/chunk-GYSK5R57.cjs.map +0 -1
- package/dist/chunk-L6ZETTX3.js.map +0 -1
- package/dist/chunk-SPDNHPM4.cjs.map +0 -1
- package/dist/chunk-TU3LVAPX.js.map +0 -1
- package/dist/chunk-WMIPYZSB.cjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkX2EQLK4O_cjs = require('./chunk-X2EQLK4O.cjs');
|
|
4
|
+
var chunkGUTKJMSF_cjs = require('./chunk-GUTKJMSF.cjs');
|
|
5
|
+
var chunkCMPJR3HS_cjs = require('./chunk-CMPJR3HS.cjs');
|
|
6
6
|
var utils_js = require('@scure/btc-signer/utils.js');
|
|
7
7
|
var btcSigner = require('@scure/btc-signer');
|
|
8
8
|
var base = require('@scure/base');
|
|
@@ -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 = chunkX2EQLK4O_cjs.getArkPsbtFields(tx.root, 0, chunkX2EQLK4O_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 = chunkX2EQLK4O_cjs.getArkPsbtFields(g.root, 0, chunkX2EQLK4O_cjs.CosignerPublicKey).map((c) => c.key);
|
|
237
237
|
const { finalKey } = aggregateKeys(cosigners, true, {
|
|
238
238
|
taprootTweak: this.scriptRoot
|
|
239
239
|
});
|
|
@@ -411,7 +411,7 @@ var SeedIdentity = class _SeedIdentity {
|
|
|
411
411
|
let network;
|
|
412
412
|
if ("descriptor" in opts && typeof opts.descriptor === "string") {
|
|
413
413
|
descriptor = opts.descriptor;
|
|
414
|
-
network =
|
|
414
|
+
network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
|
|
415
415
|
} else {
|
|
416
416
|
network = opts.isMainnet ?? true ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
|
|
417
417
|
descriptor = descriptorsScure.scriptExpressions.trBIP32({
|
|
@@ -496,7 +496,7 @@ var SeedIdentity = class _SeedIdentity {
|
|
|
496
496
|
* `StaticDescriptorProvider` for legacy single-key wallets.
|
|
497
497
|
*/
|
|
498
498
|
isOurs(descriptor) {
|
|
499
|
-
return
|
|
499
|
+
return chunkGUTKJMSF_cjs.descriptorIsOurs(descriptor, this.descriptor, utils_js.pubSchnorr(this.derivedKey));
|
|
500
500
|
}
|
|
501
501
|
/**
|
|
502
502
|
* Signs each request with the key derived from its descriptor.
|
|
@@ -533,7 +533,7 @@ var SeedIdentity = class _SeedIdentity {
|
|
|
533
533
|
}
|
|
534
534
|
// ── internal helpers ─────────────────────────────────────────────
|
|
535
535
|
derivePrivateKeyForDescriptor(descriptor) {
|
|
536
|
-
const network =
|
|
536
|
+
const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
|
|
537
537
|
const expansion = descriptorsScure.expand({ descriptor, network });
|
|
538
538
|
if (expansion.isRanged) {
|
|
539
539
|
throw new Error(
|
|
@@ -619,7 +619,7 @@ var ReadonlyDescriptorIdentity = class _ReadonlyDescriptorIdentity {
|
|
|
619
619
|
*/
|
|
620
620
|
descriptor;
|
|
621
621
|
constructor(descriptor) {
|
|
622
|
-
const network =
|
|
622
|
+
const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
|
|
623
623
|
let expansion;
|
|
624
624
|
try {
|
|
625
625
|
expansion = descriptorsScure.expand({ descriptor, network, index: 0 });
|
|
@@ -669,7 +669,7 @@ var ReadonlyDescriptorIdentity = class _ReadonlyDescriptorIdentity {
|
|
|
669
669
|
* `StaticDescriptorProvider` for legacy single-key wallets.
|
|
670
670
|
*/
|
|
671
671
|
isOurs(descriptor) {
|
|
672
|
-
return
|
|
672
|
+
return chunkGUTKJMSF_cjs.descriptorIsOurs(descriptor, this.descriptor, this.indexZero.pubkey);
|
|
673
673
|
}
|
|
674
674
|
};
|
|
675
675
|
function serializeSeedOwnedSigningIdentity(identity) {
|
|
@@ -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 chunkX2EQLK4O_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: chunkX2EQLK4O_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 chunkX2EQLK4O_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)) {
|
|
@@ -1031,10 +1031,10 @@ var ESPLORA_URL = {
|
|
|
1031
1031
|
testnet: "https://mempool.space/testnet/api",
|
|
1032
1032
|
signet: "https://mempool.signet.arkade.sh/api",
|
|
1033
1033
|
mutinynet: "https://mempool.mutinynet.arkade.sh/api",
|
|
1034
|
-
regtest: "http://localhost:3000"
|
|
1034
|
+
regtest: "http://localhost:3000/api"
|
|
1035
1035
|
};
|
|
1036
1036
|
var EsploraProvider = class {
|
|
1037
|
-
constructor(baseUrl = ESPLORA_URL[
|
|
1037
|
+
constructor(baseUrl = ESPLORA_URL[chunkCMPJR3HS_cjs.DEFAULT_NETWORK_NAME], opts) {
|
|
1038
1038
|
this.baseUrl = baseUrl;
|
|
1039
1039
|
this.pollingInterval = opts?.pollingInterval ?? 15e3;
|
|
1040
1040
|
this.forcePolling = opts?.forcePolling ?? false;
|
|
@@ -1042,14 +1042,17 @@ var EsploraProvider = class {
|
|
|
1042
1042
|
pollingInterval;
|
|
1043
1043
|
forcePolling;
|
|
1044
1044
|
async getCoins(address) {
|
|
1045
|
-
const response = await
|
|
1045
|
+
const response = await chunkX2EQLK4O_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 chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/fee-estimates`);
|
|
1053
|
+
if (response.status === 404) {
|
|
1054
|
+
return void 0;
|
|
1055
|
+
}
|
|
1053
1056
|
if (!response.ok) {
|
|
1054
1057
|
throw new Error(`Failed to fetch fee rate: ${response.statusText}`);
|
|
1055
1058
|
}
|
|
@@ -1067,7 +1070,7 @@ var EsploraProvider = class {
|
|
|
1067
1070
|
}
|
|
1068
1071
|
}
|
|
1069
1072
|
async getTxOutspends(txid) {
|
|
1070
|
-
const response = await
|
|
1073
|
+
const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/outspends`);
|
|
1071
1074
|
if (!response.ok) {
|
|
1072
1075
|
const error = await response.text();
|
|
1073
1076
|
throw new Error(`Failed to get transaction outspends: ${error}`);
|
|
@@ -1075,7 +1078,7 @@ var EsploraProvider = class {
|
|
|
1075
1078
|
return response.json();
|
|
1076
1079
|
}
|
|
1077
1080
|
async getTransactions(address) {
|
|
1078
|
-
const response = await
|
|
1081
|
+
const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/address/${address}/txs`);
|
|
1079
1082
|
if (!response.ok) {
|
|
1080
1083
|
const error = await response.text();
|
|
1081
1084
|
throw new Error(`Failed to get transactions: ${error}`);
|
|
@@ -1083,7 +1086,7 @@ var EsploraProvider = class {
|
|
|
1083
1086
|
return response.json();
|
|
1084
1087
|
}
|
|
1085
1088
|
async getTxStatus(txid) {
|
|
1086
|
-
const txresponse = await
|
|
1089
|
+
const txresponse = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}`);
|
|
1087
1090
|
if (!txresponse.ok) {
|
|
1088
1091
|
throw new Error(txresponse.statusText);
|
|
1089
1092
|
}
|
|
@@ -1091,7 +1094,7 @@ var EsploraProvider = class {
|
|
|
1091
1094
|
if (!tx.status.confirmed) {
|
|
1092
1095
|
return { confirmed: false };
|
|
1093
1096
|
}
|
|
1094
|
-
const response = await
|
|
1097
|
+
const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx/${txid}/status`);
|
|
1095
1098
|
if (!response.ok) {
|
|
1096
1099
|
throw new Error(`Failed to get transaction status: ${response.statusText}`);
|
|
1097
1100
|
}
|
|
@@ -1175,7 +1178,7 @@ var EsploraProvider = class {
|
|
|
1175
1178
|
return stopFunc;
|
|
1176
1179
|
}
|
|
1177
1180
|
async getChainTip() {
|
|
1178
|
-
const tipBlocks = await
|
|
1181
|
+
const tipBlocks = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/blocks`);
|
|
1179
1182
|
if (!tipBlocks.ok) {
|
|
1180
1183
|
throw new Error(`Failed to get chain tip: ${tipBlocks.statusText}`);
|
|
1181
1184
|
}
|
|
@@ -1194,7 +1197,7 @@ var EsploraProvider = class {
|
|
|
1194
1197
|
};
|
|
1195
1198
|
}
|
|
1196
1199
|
async broadcastPackage(parent, child) {
|
|
1197
|
-
const response = await
|
|
1200
|
+
const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/txs/package`, {
|
|
1198
1201
|
method: "POST",
|
|
1199
1202
|
headers: {
|
|
1200
1203
|
"Content-Type": "application/json"
|
|
@@ -1208,7 +1211,7 @@ var EsploraProvider = class {
|
|
|
1208
1211
|
return response.json();
|
|
1209
1212
|
}
|
|
1210
1213
|
async broadcastTx(tx) {
|
|
1211
|
-
const response = await
|
|
1214
|
+
const response = await chunkX2EQLK4O_cjs.baseFetch(`${this.baseUrl}/tx`, {
|
|
1212
1215
|
method: "POST",
|
|
1213
1216
|
headers: {
|
|
1214
1217
|
"Content-Type": "text/plain"
|
|
@@ -1277,7 +1280,7 @@ function buildForfeitTx(inputs, forfeitPkScript, txLocktime) {
|
|
|
1277
1280
|
);
|
|
1278
1281
|
}
|
|
1279
1282
|
function buildForfeitTxWithOutput(inputs, output, txLocktime) {
|
|
1280
|
-
const tx = new
|
|
1283
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction({
|
|
1281
1284
|
version: 3,
|
|
1282
1285
|
lockTime: txLocktime
|
|
1283
1286
|
});
|
|
@@ -1353,7 +1356,7 @@ function validateVtxoTxGraph(graph, roundTransaction, sweepTapTreeRoot) {
|
|
|
1353
1356
|
if (previousScriptKey.length !== 32) {
|
|
1354
1357
|
throw new Error(`parent output ${childIndex} has invalid script`);
|
|
1355
1358
|
}
|
|
1356
|
-
const cosigners =
|
|
1359
|
+
const cosigners = chunkX2EQLK4O_cjs.getArkPsbtFields(child.root, 0, chunkX2EQLK4O_cjs.CosignerPublicKey);
|
|
1357
1360
|
if (cosigners.length === 0) {
|
|
1358
1361
|
throw ErrMissingCosignersPublicKeys;
|
|
1359
1362
|
}
|
|
@@ -1453,7 +1456,7 @@ var Extension = class _Extension {
|
|
|
1453
1456
|
`expected magic prefix ${base.hex.encode(ARKADE_MAGIC)}, got ${base.hex.encode(payload.slice(0, Math.min(payload.length, ARKADE_MAGIC.length)))}`
|
|
1454
1457
|
);
|
|
1455
1458
|
}
|
|
1456
|
-
const reader = new
|
|
1459
|
+
const reader = new chunkX2EQLK4O_cjs.BufferReader(payload.slice(ARKADE_MAGIC.length));
|
|
1457
1460
|
const packets = [];
|
|
1458
1461
|
while (reader.remaining() > 0) {
|
|
1459
1462
|
const packetType = reader.readByte();
|
|
@@ -1527,7 +1530,7 @@ var Extension = class _Extension {
|
|
|
1527
1530
|
*/
|
|
1528
1531
|
getAssetPacket() {
|
|
1529
1532
|
for (const p of this.packets) {
|
|
1530
|
-
if (p instanceof
|
|
1533
|
+
if (p instanceof chunkX2EQLK4O_cjs.Packet) {
|
|
1531
1534
|
return p;
|
|
1532
1535
|
}
|
|
1533
1536
|
}
|
|
@@ -1535,8 +1538,8 @@ var Extension = class _Extension {
|
|
|
1535
1538
|
}
|
|
1536
1539
|
};
|
|
1537
1540
|
function parsePacket(packetType, data) {
|
|
1538
|
-
if (packetType ===
|
|
1539
|
-
return
|
|
1541
|
+
if (packetType === chunkX2EQLK4O_cjs.Packet.PACKET_TYPE) {
|
|
1542
|
+
return chunkX2EQLK4O_cjs.Packet.fromBytes(data);
|
|
1540
1543
|
}
|
|
1541
1544
|
return new UnknownPacket(packetType, data);
|
|
1542
1545
|
}
|
|
@@ -1594,7 +1597,7 @@ function validateBatchRecipients(commitmentTx, vtxoTreeLeaves, recipients, netwo
|
|
|
1594
1597
|
for (const recipient of recipients) {
|
|
1595
1598
|
let arkAddress;
|
|
1596
1599
|
try {
|
|
1597
|
-
arkAddress =
|
|
1600
|
+
arkAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(recipient.address);
|
|
1598
1601
|
} catch {
|
|
1599
1602
|
validateOnchainRecipient(commitmentTx, recipient, network, usedOnchainOutputs);
|
|
1600
1603
|
continue;
|
|
@@ -1727,7 +1730,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1727
1730
|
const existing = inputsByAssetId.get(asset.assetId);
|
|
1728
1731
|
inputsByAssetId.set(asset.assetId, [
|
|
1729
1732
|
...existing ?? [],
|
|
1730
|
-
|
|
1733
|
+
chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount)
|
|
1731
1734
|
]);
|
|
1732
1735
|
}
|
|
1733
1736
|
}
|
|
@@ -1739,7 +1742,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1739
1742
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
1740
1743
|
outputsByAssetId.set(asset.assetId, [
|
|
1741
1744
|
...existing ?? [],
|
|
1742
|
-
|
|
1745
|
+
chunkX2EQLK4O_cjs.AssetOutput.create(outputIndex, asset.amount)
|
|
1743
1746
|
]);
|
|
1744
1747
|
}
|
|
1745
1748
|
}
|
|
@@ -1750,7 +1753,7 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1750
1753
|
const existing = outputsByAssetId.get(asset.assetId);
|
|
1751
1754
|
outputsByAssetId.set(asset.assetId, [
|
|
1752
1755
|
...existing ?? [],
|
|
1753
|
-
|
|
1756
|
+
chunkX2EQLK4O_cjs.AssetOutput.create(outputIndex, asset.amount)
|
|
1754
1757
|
]);
|
|
1755
1758
|
}
|
|
1756
1759
|
}
|
|
@@ -1759,11 +1762,11 @@ function createAssetPacket(assetInputs, receivers, changeReceiver) {
|
|
|
1759
1762
|
for (const assetIdStr of allAssetIds) {
|
|
1760
1763
|
const inputs = inputsByAssetId.get(assetIdStr);
|
|
1761
1764
|
const outputs = outputsByAssetId.get(assetIdStr);
|
|
1762
|
-
const assetId =
|
|
1763
|
-
const group =
|
|
1765
|
+
const assetId = chunkX2EQLK4O_cjs.AssetId.fromString(assetIdStr);
|
|
1766
|
+
const group = chunkX2EQLK4O_cjs.AssetGroup.create(assetId, null, inputs ?? [], outputs ?? [], []);
|
|
1764
1767
|
groups.push(group);
|
|
1765
1768
|
}
|
|
1766
|
-
return
|
|
1769
|
+
return chunkX2EQLK4O_cjs.Packet.create(groups);
|
|
1767
1770
|
}
|
|
1768
1771
|
function selectCoinsWithAsset(coins, assetId, requiredAmount) {
|
|
1769
1772
|
const coinsWithAsset = coins.filter((coin) => coin.assets?.some((a) => a.assetId === assetId));
|
|
@@ -1798,6 +1801,45 @@ function selectedCoinsToAssetInputs(selectedCoins) {
|
|
|
1798
1801
|
}
|
|
1799
1802
|
return assetInputs;
|
|
1800
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
|
+
}
|
|
1801
1843
|
function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
1802
1844
|
const MAX_OP_RETURN = 2;
|
|
1803
1845
|
let countOpReturn = 0;
|
|
@@ -1829,8 +1871,8 @@ function buildOffchainTx(inputs, outputs, serverUnrollScript) {
|
|
|
1829
1871
|
function buildVirtualTx(inputs, outputs) {
|
|
1830
1872
|
let lockTime = 0n;
|
|
1831
1873
|
for (const input of inputs) {
|
|
1832
|
-
const tapscript =
|
|
1833
|
-
if (
|
|
1874
|
+
const tapscript = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(input.tapLeafScript));
|
|
1875
|
+
if (chunkCMPJR3HS_cjs.CLTVMultisigTapscript.is(tapscript)) {
|
|
1834
1876
|
if (lockTime !== 0n) {
|
|
1835
1877
|
if (isSeconds(lockTime) !== isSeconds(tapscript.params.absoluteTimelock)) {
|
|
1836
1878
|
throw new Error("cannot mix seconds and blocks locktime");
|
|
@@ -1841,7 +1883,7 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
1841
1883
|
}
|
|
1842
1884
|
}
|
|
1843
1885
|
}
|
|
1844
|
-
const tx = new
|
|
1886
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction({
|
|
1845
1887
|
version: 3,
|
|
1846
1888
|
lockTime: Number(lockTime)
|
|
1847
1889
|
});
|
|
@@ -1851,12 +1893,12 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
1851
1893
|
index: input.vout,
|
|
1852
1894
|
sequence: lockTime ? btcSigner.DEFAULT_SEQUENCE - 1 : void 0,
|
|
1853
1895
|
witnessUtxo: {
|
|
1854
|
-
script:
|
|
1896
|
+
script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript,
|
|
1855
1897
|
amount: BigInt(input.value)
|
|
1856
1898
|
},
|
|
1857
1899
|
tapLeafScript: [input.tapLeafScript]
|
|
1858
1900
|
});
|
|
1859
|
-
|
|
1901
|
+
chunkX2EQLK4O_cjs.setArkPsbtField(tx, i, chunkX2EQLK4O_cjs.VtxoTaprootTree, input.tapTree);
|
|
1860
1902
|
}
|
|
1861
1903
|
for (const output of outputs) {
|
|
1862
1904
|
tx.addOutput(output);
|
|
@@ -1865,8 +1907,8 @@ function buildVirtualTx(inputs, outputs) {
|
|
|
1865
1907
|
return tx;
|
|
1866
1908
|
}
|
|
1867
1909
|
function buildCheckpointTx(vtxo, serverUnrollScript) {
|
|
1868
|
-
const collaborativeClosure =
|
|
1869
|
-
const checkpointVtxoScript = new
|
|
1910
|
+
const collaborativeClosure = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(vtxo.tapLeafScript));
|
|
1911
|
+
const checkpointVtxoScript = new chunkCMPJR3HS_cjs.VtxoScript([
|
|
1870
1912
|
serverUnrollScript.script,
|
|
1871
1913
|
collaborativeClosure.script
|
|
1872
1914
|
]);
|
|
@@ -2010,7 +2052,7 @@ function combineTapscriptSigs(signedTx, originalTx) {
|
|
|
2010
2052
|
}
|
|
2011
2053
|
function isValidArkAddress(address) {
|
|
2012
2054
|
try {
|
|
2013
|
-
|
|
2055
|
+
chunkCMPJR3HS_cjs.ArkAddress.decode(address);
|
|
2014
2056
|
return true;
|
|
2015
2057
|
} catch (e) {
|
|
2016
2058
|
return false;
|
|
@@ -2280,16 +2322,16 @@ var FALLBACK_WALLET_DUST_AMOUNT = 330n;
|
|
|
2280
2322
|
function getDustAmount(wallet) {
|
|
2281
2323
|
return "dustAmount" in wallet ? wallet.dustAmount : FALLBACK_WALLET_DUST_AMOUNT;
|
|
2282
2324
|
}
|
|
2283
|
-
function
|
|
2325
|
+
function extendCoinWithTapscript(boardingTapscript, utxo) {
|
|
2284
2326
|
return {
|
|
2285
2327
|
...utxo,
|
|
2286
|
-
forfeitTapLeafScript:
|
|
2287
|
-
intentTapLeafScript:
|
|
2288
|
-
tapTree:
|
|
2328
|
+
forfeitTapLeafScript: boardingTapscript.forfeit(),
|
|
2329
|
+
intentTapLeafScript: boardingTapscript.forfeit(),
|
|
2330
|
+
tapTree: boardingTapscript.encode()
|
|
2289
2331
|
};
|
|
2290
2332
|
}
|
|
2291
2333
|
function deriveContractTapscripts(contract) {
|
|
2292
|
-
const handler =
|
|
2334
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
|
|
2293
2335
|
if (!handler) {
|
|
2294
2336
|
throw new Error(`No handler for contract type '${contract.type}'`);
|
|
2295
2337
|
}
|
|
@@ -2359,7 +2401,7 @@ function validateRecipients(recipients, dustAmount) {
|
|
|
2359
2401
|
for (const recipient of recipients) {
|
|
2360
2402
|
let address;
|
|
2361
2403
|
try {
|
|
2362
|
-
address =
|
|
2404
|
+
address = chunkCMPJR3HS_cjs.ArkAddress.decode(recipient.address);
|
|
2363
2405
|
} catch (e) {
|
|
2364
2406
|
throw new Error(`Invalid Arkade address: ${recipient.address}`);
|
|
2365
2407
|
}
|
|
@@ -2378,13 +2420,27 @@ function validateRecipients(recipients, dustAmount) {
|
|
|
2378
2420
|
}
|
|
2379
2421
|
|
|
2380
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
|
+
}
|
|
2381
2437
|
function isSweepCapable(wallet) {
|
|
2382
|
-
return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet;
|
|
2438
|
+
return "boardingTapscript" in wallet && "onchainProvider" in wallet && "arkProvider" in wallet && "network" in wallet && "signOnchainBoardingTx" in wallet;
|
|
2383
2439
|
}
|
|
2384
2440
|
function assertSweepCapable(wallet) {
|
|
2385
2441
|
if (!isSweepCapable(wallet)) {
|
|
2386
2442
|
throw new Error(
|
|
2387
|
-
"Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, and
|
|
2443
|
+
"Boarding UTXO sweep requires a Wallet instance with boardingTapscript, onchainProvider, arkProvider, network, and signOnchainBoardingTx"
|
|
2388
2444
|
);
|
|
2389
2445
|
}
|
|
2390
2446
|
}
|
|
@@ -2400,6 +2456,33 @@ async function runWithCrossInstanceLock(name, fn) {
|
|
|
2400
2456
|
await fn();
|
|
2401
2457
|
});
|
|
2402
2458
|
}
|
|
2459
|
+
var MAX_VTXOS_PER_SETTLEMENT = 50;
|
|
2460
|
+
function byValueDescending(vtxos) {
|
|
2461
|
+
return [...vtxos].sort((a, b) => b.value - a.value);
|
|
2462
|
+
}
|
|
2463
|
+
function byExpiryAscending(vtxos) {
|
|
2464
|
+
const expiryKey = (vtxo) => {
|
|
2465
|
+
if (isRecoverable(vtxo)) return -Infinity;
|
|
2466
|
+
const batchExpiry = vtxo.virtualStatus.batchExpiry;
|
|
2467
|
+
if (isExpired(vtxo)) return batchExpiry ?? -Infinity;
|
|
2468
|
+
if (!batchExpiry) return Infinity;
|
|
2469
|
+
if (new Date(batchExpiry).getFullYear() < 2025) return Infinity;
|
|
2470
|
+
return batchExpiry;
|
|
2471
|
+
};
|
|
2472
|
+
return [...vtxos].sort((a, b) => expiryKey(a) - expiryKey(b));
|
|
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
|
+
}
|
|
2403
2486
|
var DEFAULT_THRESHOLD_SECONDS = 259200;
|
|
2404
2487
|
var DEFAULT_THRESHOLD_MS = DEFAULT_THRESHOLD_SECONDS * 1e3;
|
|
2405
2488
|
var DEFAULT_RENEWAL_CONFIG = {
|
|
@@ -2409,7 +2492,8 @@ var DEFAULT_RENEWAL_CONFIG = {
|
|
|
2409
2492
|
var DEFAULT_SETTLEMENT_CONFIG = {
|
|
2410
2493
|
vtxoThreshold: DEFAULT_THRESHOLD_SECONDS,
|
|
2411
2494
|
boardingUtxoSweep: true,
|
|
2412
|
-
pollIntervalMs: 6e4
|
|
2495
|
+
pollIntervalMs: 6e4,
|
|
2496
|
+
deprecatedSignerMigration: true
|
|
2413
2497
|
};
|
|
2414
2498
|
function getRecoverableVtxos(vtxos, dustAmount) {
|
|
2415
2499
|
return vtxos.filter((vtxo) => {
|
|
@@ -2463,6 +2547,51 @@ function getExpiringAndRecoverableVtxos(vtxos, thresholdMs, dustAmount) {
|
|
|
2463
2547
|
(vtxo) => isVtxoExpiringSoon(vtxo, thresholdMs) || isRecoverable(vtxo) || isSpendable(vtxo) && isExpired(vtxo) || isSubdust(vtxo, dustAmount)
|
|
2464
2548
|
);
|
|
2465
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
|
+
}
|
|
2466
2595
|
var VtxoManager = class _VtxoManager {
|
|
2467
2596
|
constructor(wallet, renewalConfig, settlementConfig) {
|
|
2468
2597
|
this.wallet = wallet;
|
|
@@ -2478,15 +2607,9 @@ var VtxoManager = class _VtxoManager {
|
|
|
2478
2607
|
} else {
|
|
2479
2608
|
this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
|
|
2480
2609
|
}
|
|
2481
|
-
this.contractEventsSubscriptionReady = this.initializeSubscription()
|
|
2482
|
-
(subscription) => {
|
|
2483
|
-
this.contractEventsSubscription = subscription;
|
|
2484
|
-
return subscription;
|
|
2485
|
-
}
|
|
2486
|
-
);
|
|
2610
|
+
this.contractEventsSubscriptionReady = this.initializeSubscription();
|
|
2487
2611
|
}
|
|
2488
2612
|
settlementConfig;
|
|
2489
|
-
contractEventsSubscription;
|
|
2490
2613
|
contractEventsSubscriptionReady;
|
|
2491
2614
|
disposePromise;
|
|
2492
2615
|
pollTimeoutId;
|
|
@@ -2523,6 +2646,15 @@ var VtxoManager = class _VtxoManager {
|
|
|
2523
2646
|
lastVtxoSpentRefreshTimestamp = 0;
|
|
2524
2647
|
vtxoSpentRefreshPromise;
|
|
2525
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;
|
|
2526
2658
|
// ========== Recovery Methods ==========
|
|
2527
2659
|
/**
|
|
2528
2660
|
* Recover swept/expired virtual outputs by settling them back to the wallet's Arkade address.
|
|
@@ -2559,10 +2691,25 @@ var VtxoManager = class _VtxoManager {
|
|
|
2559
2691
|
withUnrolled: false
|
|
2560
2692
|
});
|
|
2561
2693
|
const dustAmount = getDustAmount(this.wallet);
|
|
2562
|
-
|
|
2694
|
+
let { vtxosToRecover, totalAmount } = getRecoverableWithSubdust(allVtxos, dustAmount);
|
|
2563
2695
|
if (vtxosToRecover.length === 0) {
|
|
2564
2696
|
throw new Error("No recoverable VTXOs found");
|
|
2565
2697
|
}
|
|
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) {
|
|
2702
|
+
const recoverableCount = vtxosToRecover.length;
|
|
2703
|
+
({ vtxosToRecover, totalAmount } = getRecoverableWithSubdust(capped, dustAmount));
|
|
2704
|
+
if (vtxosToRecover.length === 0) {
|
|
2705
|
+
throw new Error(
|
|
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}`
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
if (info && isMigrationCapable(this.wallet)) {
|
|
2711
|
+
await this.rotateForRecoverableInputs(vtxosToRecover, info);
|
|
2712
|
+
}
|
|
2566
2713
|
const arkAddress = await this.wallet.getAddress();
|
|
2567
2714
|
return this.wallet.settle(
|
|
2568
2715
|
{
|
|
@@ -2712,6 +2859,25 @@ var VtxoManager = class _VtxoManager {
|
|
|
2712
2859
|
if (vtxos.length === 0) {
|
|
2713
2860
|
throw new Error("No VTXOs available to renew");
|
|
2714
2861
|
}
|
|
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
|
+
}
|
|
2880
|
+
}
|
|
2715
2881
|
const totalAmount = vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
|
|
2716
2882
|
const dustAmount = getDustAmount(this.wallet);
|
|
2717
2883
|
if (BigInt(totalAmount) < dustAmount) {
|
|
@@ -2719,6 +2885,9 @@ var VtxoManager = class _VtxoManager {
|
|
|
2719
2885
|
`Total amount ${totalAmount} is below dust threshold ${dustAmount}`
|
|
2720
2886
|
);
|
|
2721
2887
|
}
|
|
2888
|
+
if (info && isMigrationCapable(this.wallet)) {
|
|
2889
|
+
await this.rotateForRecoverableInputs(vtxos, info);
|
|
2890
|
+
}
|
|
2722
2891
|
const arkAddress = await this.wallet.getAddress();
|
|
2723
2892
|
const txid = await this.wallet.settle(
|
|
2724
2893
|
{
|
|
@@ -2819,7 +2988,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
2819
2988
|
const boardingAddress = await this.wallet.getBoardingAddress();
|
|
2820
2989
|
const feeRate = await this.getOnchainProvider().getFeeRate() ?? 1;
|
|
2821
2990
|
const exitTapLeafScript = this.getBoardingExitLeaf();
|
|
2822
|
-
const sequence = chunkWMIPYZSB_cjs.getSequence(exitTapLeafScript);
|
|
2823
2991
|
const leafScript = exitTapLeafScript[1];
|
|
2824
2992
|
const leafScriptSize = leafScript.length - 1;
|
|
2825
2993
|
const controlBlockSize = exitTapLeafScript[0].merklePath.length * 32;
|
|
@@ -2838,21 +3006,30 @@ var VtxoManager = class _VtxoManager {
|
|
|
2838
3006
|
`Sweep not economical: output ${outputAmount} sats after ${fee} sats fee is below dust (${dustAmount} sats)`
|
|
2839
3007
|
);
|
|
2840
3008
|
}
|
|
2841
|
-
const tx = new
|
|
3009
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction();
|
|
2842
3010
|
for (const utxo of expiredUtxos) {
|
|
3011
|
+
const utxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(utxo.tapTree);
|
|
3012
|
+
const utxoExitLeaf = utxoScript.leaves.find(
|
|
3013
|
+
(leaf) => chunkCMPJR3HS_cjs.CSVMultisigTapscript.isScriptValid(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(leaf)) === true
|
|
3014
|
+
);
|
|
3015
|
+
if (!utxoExitLeaf) {
|
|
3016
|
+
throw new Error(
|
|
3017
|
+
`Boarding sweep: no CSV exit leaf for UTXO ${utxo.txid}:${utxo.vout}`
|
|
3018
|
+
);
|
|
3019
|
+
}
|
|
2843
3020
|
tx.addInput({
|
|
2844
3021
|
txid: utxo.txid,
|
|
2845
3022
|
index: utxo.vout,
|
|
2846
3023
|
witnessUtxo: {
|
|
2847
|
-
script:
|
|
3024
|
+
script: utxoScript.pkScript,
|
|
2848
3025
|
amount: BigInt(utxo.value)
|
|
2849
3026
|
},
|
|
2850
|
-
tapLeafScript: [
|
|
2851
|
-
sequence
|
|
3027
|
+
tapLeafScript: [utxoExitLeaf],
|
|
3028
|
+
sequence: chunkCMPJR3HS_cjs.getSequence(utxoExitLeaf)
|
|
2852
3029
|
});
|
|
2853
3030
|
}
|
|
2854
3031
|
tx.addOutputAddress(boardingAddress, outputAmount, this.getNetwork());
|
|
2855
|
-
const signedTx = await this.
|
|
3032
|
+
const signedTx = await this.getSweepWallet().signOnchainBoardingTx(tx);
|
|
2856
3033
|
signedTx.finalize();
|
|
2857
3034
|
const txid = await this.getOnchainProvider().broadcastTransaction(signedTx.hex);
|
|
2858
3035
|
for (const u of expiredUtxos) {
|
|
@@ -2861,6 +3038,472 @@ var VtxoManager = class _VtxoManager {
|
|
|
2861
3038
|
this.knownBoardingUtxos.add(`${txid}:0`);
|
|
2862
3039
|
return txid;
|
|
2863
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
|
+
}
|
|
2864
3507
|
// ========== Private Helpers ==========
|
|
2865
3508
|
/** Asserts sweep capability and returns the typed wallet. */
|
|
2866
3509
|
getSweepWallet() {
|
|
@@ -2870,7 +3513,7 @@ var VtxoManager = class _VtxoManager {
|
|
|
2870
3513
|
/** Decodes the boarding tapscript exit path to extract the CSV timelock. */
|
|
2871
3514
|
getBoardingTimelock() {
|
|
2872
3515
|
const wallet = this.getSweepWallet();
|
|
2873
|
-
const exitScript =
|
|
3516
|
+
const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
|
|
2874
3517
|
base.hex.decode(wallet.boardingTapscript.exitScript)
|
|
2875
3518
|
);
|
|
2876
3519
|
return exitScript.params.timelock;
|
|
@@ -2879,10 +3522,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
2879
3522
|
getBoardingExitLeaf() {
|
|
2880
3523
|
return this.getSweepWallet().boardingTapscript.exit();
|
|
2881
3524
|
}
|
|
2882
|
-
/** Returns the pkScript (output script) of the boarding tapscript. */
|
|
2883
|
-
getBoardingOutputScript() {
|
|
2884
|
-
return this.getSweepWallet().boardingTapscript.pkScript;
|
|
2885
|
-
}
|
|
2886
3525
|
/** Returns the onchain provider for fee estimation and broadcasting. */
|
|
2887
3526
|
getOnchainProvider() {
|
|
2888
3527
|
return this.getSweepWallet().onchainProvider;
|
|
@@ -2891,14 +3530,20 @@ var VtxoManager = class _VtxoManager {
|
|
|
2891
3530
|
getArkProvider() {
|
|
2892
3531
|
return this.getSweepWallet().arkProvider;
|
|
2893
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
|
+
}
|
|
2894
3543
|
/** Returns the Bitcoin network configuration from the wallet. */
|
|
2895
3544
|
getNetwork() {
|
|
2896
3545
|
return this.getSweepWallet().network;
|
|
2897
3546
|
}
|
|
2898
|
-
/** Returns the wallet's identity for transaction signing. */
|
|
2899
|
-
getIdentity() {
|
|
2900
|
-
return this.wallet.identity;
|
|
2901
|
-
}
|
|
2902
3547
|
async initializeSubscription() {
|
|
2903
3548
|
if (this.settlementConfig === false) {
|
|
2904
3549
|
return void 0;
|
|
@@ -2999,7 +3644,7 @@ var VtxoManager = class _VtxoManager {
|
|
|
2999
3644
|
* or doesn't carry the metadata.
|
|
3000
3645
|
*/
|
|
3001
3646
|
extractSpentOutpoint(error) {
|
|
3002
|
-
const ark =
|
|
3647
|
+
const ark = chunkX2EQLK4O_cjs.maybeArkError(error);
|
|
3003
3648
|
if (!ark || ark.name !== "VTXO_ALREADY_SPENT") return void 0;
|
|
3004
3649
|
const raw = ark.metadata?.vtxo_outpoint;
|
|
3005
3650
|
if (typeof raw !== "string") return void 0;
|
|
@@ -3096,6 +3741,10 @@ var VtxoManager = class _VtxoManager {
|
|
|
3096
3741
|
}
|
|
3097
3742
|
}
|
|
3098
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
|
+
}
|
|
3099
3748
|
});
|
|
3100
3749
|
} catch (e) {
|
|
3101
3750
|
hadError = true;
|
|
@@ -3164,7 +3813,8 @@ var VtxoManager = class _VtxoManager {
|
|
|
3164
3813
|
return;
|
|
3165
3814
|
}
|
|
3166
3815
|
const dustAmount = getDustAmount(this.wallet);
|
|
3167
|
-
const
|
|
3816
|
+
const info = await this.getArkProvider().getInfo();
|
|
3817
|
+
const { fees, vtxoMaxAmount } = info;
|
|
3168
3818
|
const estimator = new Estimator(fees.intentFee);
|
|
3169
3819
|
let totalAmount = 0n;
|
|
3170
3820
|
const filteredBoarding = [];
|
|
@@ -3179,7 +3829,10 @@ var VtxoManager = class _VtxoManager {
|
|
|
3179
3829
|
totalAmount += BigInt(u.value) - BigInt(inputFee.satoshis);
|
|
3180
3830
|
}
|
|
3181
3831
|
const filteredVtxos = [];
|
|
3182
|
-
for (const v of expiringVtxos) {
|
|
3832
|
+
for (const v of byExpiryAscending(expiringVtxos)) {
|
|
3833
|
+
if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
|
|
3834
|
+
break;
|
|
3835
|
+
}
|
|
3183
3836
|
const inputFee = estimator.evalOffchainInput({
|
|
3184
3837
|
amount: BigInt(v.value),
|
|
3185
3838
|
type: v.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
|
|
@@ -3190,16 +3843,23 @@ var VtxoManager = class _VtxoManager {
|
|
|
3190
3843
|
if (inputFee.satoshis >= v.value) {
|
|
3191
3844
|
continue;
|
|
3192
3845
|
}
|
|
3846
|
+
const net = BigInt(v.value) - BigInt(inputFee.satoshis);
|
|
3847
|
+
if (vtxoMaxAmount >= 0n && totalAmount + net > vtxoMaxAmount) {
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3193
3850
|
filteredVtxos.push(v);
|
|
3194
|
-
totalAmount +=
|
|
3851
|
+
totalAmount += net;
|
|
3195
3852
|
}
|
|
3196
3853
|
if (filteredBoarding.length === 0 && filteredVtxos.length === 0) {
|
|
3197
3854
|
return;
|
|
3198
3855
|
}
|
|
3856
|
+
if (isMigrationCapable(this.wallet)) {
|
|
3857
|
+
await this.rotateForRecoverableInputs([...filteredBoarding, ...filteredVtxos], info);
|
|
3858
|
+
}
|
|
3199
3859
|
const arkAddress = await this.wallet.getAddress();
|
|
3200
3860
|
const outputFee = estimator.evalOffchainOutput({
|
|
3201
3861
|
amount: totalAmount,
|
|
3202
|
-
script: base.hex.encode(
|
|
3862
|
+
script: base.hex.encode(chunkCMPJR3HS_cjs.ArkAddress.decode(arkAddress).pkScript)
|
|
3203
3863
|
});
|
|
3204
3864
|
totalAmount -= BigInt(outputFee.satoshis);
|
|
3205
3865
|
if (totalAmount < dustAmount) return;
|
|
@@ -3258,7 +3918,6 @@ var VtxoManager = class _VtxoManager {
|
|
|
3258
3918
|
clearTimeout(timer);
|
|
3259
3919
|
}
|
|
3260
3920
|
const subscription = await this.contractEventsSubscriptionReady;
|
|
3261
|
-
this.contractEventsSubscription = void 0;
|
|
3262
3921
|
subscription?.();
|
|
3263
3922
|
})();
|
|
3264
3923
|
return this.disposePromise;
|
|
@@ -3280,7 +3939,7 @@ var ArkNote = class _ArkNote {
|
|
|
3280
3939
|
this.value = value;
|
|
3281
3940
|
this.HRP = HRP;
|
|
3282
3941
|
const preimageHash = utils_js.sha256(this.preimage);
|
|
3283
|
-
this.vtxoScript = new
|
|
3942
|
+
this.vtxoScript = new chunkCMPJR3HS_cjs.VtxoScript([noteTapscript(preimageHash)]);
|
|
3284
3943
|
const leaf = this.vtxoScript.leaves[0];
|
|
3285
3944
|
this.txid = base.hex.encode(new Uint8Array(preimageHash).reverse());
|
|
3286
3945
|
this.tapTree = this.vtxoScript.encode();
|
|
@@ -3799,15 +4458,17 @@ async function buildTransactionHistory(vtxos, allBoardingTxs, commitmentsToIgnor
|
|
|
3799
4458
|
txTime = getTxCreatedAt ? await getTxCreatedAt(vtxo.arkTxId) ?? vtxo.createdAt.getTime() + 1 : vtxo.createdAt.getTime() + 1;
|
|
3800
4459
|
}
|
|
3801
4460
|
const assets = subtractAssets(allSpent, changes);
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
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
|
+
}
|
|
3811
4472
|
}
|
|
3812
4473
|
if (vtxo.settledBy && !commitmentsToIgnore.has(vtxo.settledBy) && !sent.some((s) => s.key.commitmentTxid === vtxo.settledBy)) {
|
|
3813
4474
|
const changes = fromOldestVtxo.filter(
|
|
@@ -3894,7 +4555,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3894
4555
|
const virtualCoins = await this.wallet.getVtxos({
|
|
3895
4556
|
withRecoverable: false
|
|
3896
4557
|
});
|
|
3897
|
-
const controlAssetRef = params.controlAssetId ?
|
|
4558
|
+
const controlAssetRef = params.controlAssetId ? chunkX2EQLK4O_cjs.AssetRef.fromId(chunkX2EQLK4O_cjs.AssetId.fromString(params.controlAssetId)) : null;
|
|
3898
4559
|
const coinSelection = selectVirtualCoins(virtualCoins, Number(this.wallet.dustAmount));
|
|
3899
4560
|
let totalBtcSelected = 0n;
|
|
3900
4561
|
const assetChanges = /* @__PURE__ */ new Map();
|
|
@@ -3907,8 +4568,8 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3907
4568
|
}
|
|
3908
4569
|
}
|
|
3909
4570
|
const groups = [];
|
|
3910
|
-
const issuedAssetOutput =
|
|
3911
|
-
const issuedAssetGroup =
|
|
4571
|
+
const issuedAssetOutput = chunkX2EQLK4O_cjs.AssetOutput.create(0, params.amount);
|
|
4572
|
+
const issuedAssetGroup = chunkX2EQLK4O_cjs.AssetGroup.create(
|
|
3912
4573
|
null,
|
|
3913
4574
|
controlAssetRef,
|
|
3914
4575
|
[],
|
|
@@ -3923,28 +4584,28 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3923
4584
|
for (const [inputIndex, assets] of assetInputs) {
|
|
3924
4585
|
for (const asset of assets) {
|
|
3925
4586
|
if (asset.assetId !== assetId) continue;
|
|
3926
|
-
changeInputs.push(
|
|
4587
|
+
changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
3927
4588
|
}
|
|
3928
4589
|
}
|
|
3929
4590
|
groups.push(
|
|
3930
|
-
|
|
3931
|
-
|
|
4591
|
+
chunkX2EQLK4O_cjs.AssetGroup.create(
|
|
4592
|
+
chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
|
|
3932
4593
|
null,
|
|
3933
4594
|
changeInputs,
|
|
3934
|
-
[
|
|
4595
|
+
[chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)],
|
|
3935
4596
|
[]
|
|
3936
4597
|
)
|
|
3937
4598
|
);
|
|
3938
4599
|
}
|
|
3939
4600
|
}
|
|
3940
4601
|
const address = await this.wallet.getAddress();
|
|
3941
|
-
const outputAddress =
|
|
4602
|
+
const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
|
|
3942
4603
|
const outputs = [
|
|
3943
4604
|
{
|
|
3944
4605
|
script: outputAddress.pkScript,
|
|
3945
4606
|
amount: BigInt(totalBtcSelected)
|
|
3946
4607
|
},
|
|
3947
|
-
Extension.create([
|
|
4608
|
+
Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
|
|
3948
4609
|
];
|
|
3949
4610
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(
|
|
3950
4611
|
coinSelection.inputs,
|
|
@@ -3952,7 +4613,7 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
3952
4613
|
);
|
|
3953
4614
|
return {
|
|
3954
4615
|
arkTxId: arkTxid,
|
|
3955
|
-
assetId:
|
|
4616
|
+
assetId: chunkX2EQLK4O_cjs.AssetId.create(arkTxid, 0).toString()
|
|
3956
4617
|
};
|
|
3957
4618
|
}
|
|
3958
4619
|
/**
|
|
@@ -4024,16 +4685,16 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4024
4685
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4025
4686
|
for (const asset of assets) {
|
|
4026
4687
|
if (asset.assetId !== params.assetId) continue;
|
|
4027
|
-
reissueInputs.push(
|
|
4688
|
+
reissueInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4028
4689
|
}
|
|
4029
4690
|
}
|
|
4030
4691
|
const totalAssetAmount = assetToReissueAmount + params.amount;
|
|
4031
|
-
const reissueAssetIdObj =
|
|
4032
|
-
const reissueAssetGroup =
|
|
4692
|
+
const reissueAssetIdObj = chunkX2EQLK4O_cjs.AssetId.fromString(params.assetId);
|
|
4693
|
+
const reissueAssetGroup = chunkX2EQLK4O_cjs.AssetGroup.create(
|
|
4033
4694
|
reissueAssetIdObj,
|
|
4034
4695
|
null,
|
|
4035
4696
|
reissueInputs,
|
|
4036
|
-
[
|
|
4697
|
+
[chunkX2EQLK4O_cjs.AssetOutput.create(0, totalAssetAmount)],
|
|
4037
4698
|
[]
|
|
4038
4699
|
);
|
|
4039
4700
|
const groups = [reissueAssetGroup];
|
|
@@ -4042,27 +4703,27 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4042
4703
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4043
4704
|
for (const asset of assets) {
|
|
4044
4705
|
if (asset.assetId !== assetId) continue;
|
|
4045
|
-
changeInputs.push(
|
|
4706
|
+
changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4046
4707
|
}
|
|
4047
4708
|
}
|
|
4048
4709
|
groups.push(
|
|
4049
|
-
|
|
4050
|
-
|
|
4710
|
+
chunkX2EQLK4O_cjs.AssetGroup.create(
|
|
4711
|
+
chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
|
|
4051
4712
|
null,
|
|
4052
4713
|
changeInputs,
|
|
4053
|
-
[
|
|
4714
|
+
[chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)],
|
|
4054
4715
|
[]
|
|
4055
4716
|
)
|
|
4056
4717
|
);
|
|
4057
4718
|
}
|
|
4058
4719
|
const address = await this.wallet.getAddress();
|
|
4059
|
-
const outputAddress =
|
|
4720
|
+
const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
|
|
4060
4721
|
const outputs = [
|
|
4061
4722
|
{
|
|
4062
4723
|
script: outputAddress.pkScript,
|
|
4063
4724
|
amount: BigInt(totalBtcSelected)
|
|
4064
4725
|
},
|
|
4065
|
-
Extension.create([
|
|
4726
|
+
Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
|
|
4066
4727
|
];
|
|
4067
4728
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
4068
4729
|
return arkTxid;
|
|
@@ -4129,27 +4790,27 @@ var AssetManager = class extends ReadonlyAssetManager {
|
|
|
4129
4790
|
for (const [inputIndex, assets] of assetInputs) {
|
|
4130
4791
|
for (const asset of assets) {
|
|
4131
4792
|
if (asset.assetId !== assetId) continue;
|
|
4132
|
-
changeInputs.push(
|
|
4793
|
+
changeInputs.push(chunkX2EQLK4O_cjs.AssetInput.create(inputIndex, asset.amount));
|
|
4133
4794
|
}
|
|
4134
4795
|
}
|
|
4135
4796
|
groups.push(
|
|
4136
|
-
|
|
4137
|
-
|
|
4797
|
+
chunkX2EQLK4O_cjs.AssetGroup.create(
|
|
4798
|
+
chunkX2EQLK4O_cjs.AssetId.fromString(assetId),
|
|
4138
4799
|
null,
|
|
4139
4800
|
changeInputs,
|
|
4140
|
-
amount > 0n ? [
|
|
4801
|
+
amount > 0n ? [chunkX2EQLK4O_cjs.AssetOutput.create(0, amount)] : [],
|
|
4141
4802
|
[]
|
|
4142
4803
|
)
|
|
4143
4804
|
);
|
|
4144
4805
|
}
|
|
4145
4806
|
const address = await this.wallet.getAddress();
|
|
4146
|
-
const outputAddress =
|
|
4807
|
+
const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(address);
|
|
4147
4808
|
const outputs = [
|
|
4148
4809
|
{
|
|
4149
4810
|
script: outputAddress.pkScript,
|
|
4150
4811
|
amount: BigInt(totalBtcSelected)
|
|
4151
4812
|
},
|
|
4152
|
-
Extension.create([
|
|
4813
|
+
Extension.create([chunkX2EQLK4O_cjs.Packet.create(groups)]).txOut()
|
|
4153
4814
|
];
|
|
4154
4815
|
const { arkTxid } = await this.wallet.buildAndSubmitOffchainTx(selectedCoins, outputs);
|
|
4155
4816
|
return arkTxid;
|
|
@@ -4174,7 +4835,7 @@ function castMetadata(metadata) {
|
|
|
4174
4835
|
} else {
|
|
4175
4836
|
throw new Error("Invalid metadata value type");
|
|
4176
4837
|
}
|
|
4177
|
-
md.push(
|
|
4838
|
+
md.push(chunkX2EQLK4O_cjs.Metadata.create(textEncoder.encode(key), valueBytes));
|
|
4178
4839
|
}
|
|
4179
4840
|
return md;
|
|
4180
4841
|
}
|
|
@@ -4192,7 +4853,7 @@ var DelegateManagerImpl = class {
|
|
|
4192
4853
|
if (vtxos.length === 0) {
|
|
4193
4854
|
return { delegated: [], failed: [] };
|
|
4194
4855
|
}
|
|
4195
|
-
const destinationScript =
|
|
4856
|
+
const destinationScript = chunkCMPJR3HS_cjs.ArkAddress.decode(destination).pkScript;
|
|
4196
4857
|
const arkInfo = await this.arkInfoProvider.getInfo();
|
|
4197
4858
|
const delegateInfo = await this.delegateProvider.getDelegateInfo();
|
|
4198
4859
|
const eligible = vtxos.filter(
|
|
@@ -4340,7 +5001,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
|
|
|
4340
5001
|
const delegateFee = BigInt(Number(fee));
|
|
4341
5002
|
if (delegateFee > 0n) {
|
|
4342
5003
|
outputs.push({
|
|
4343
|
-
script:
|
|
5004
|
+
script: chunkCMPJR3HS_cjs.ArkAddress.decode(delegateAddress).pkScript,
|
|
4344
5005
|
amount: delegateFee
|
|
4345
5006
|
});
|
|
4346
5007
|
}
|
|
@@ -4373,7 +5034,7 @@ async function delegate(identity, delegateProvider, arkInfo, delegateInfo, vtxos
|
|
|
4373
5034
|
destinationScript
|
|
4374
5035
|
);
|
|
4375
5036
|
const forfeitOutputScript = btcSigner.OutScript.encode(
|
|
4376
|
-
btcSigner.Address(
|
|
5037
|
+
btcSigner.Address(chunkCMPJR3HS_cjs.getNetwork(network)).decode(forfeitAddress)
|
|
4377
5038
|
);
|
|
4378
5039
|
const forfeits = await Promise.all(
|
|
4379
5040
|
vtxos.filter((v) => !isRecoverable(v)).map(async (coin) => {
|
|
@@ -4401,7 +5062,7 @@ async function makeDelegateForfeitTx(input, connectorAmount, delegatePubkey, for
|
|
|
4401
5062
|
index: input.vout,
|
|
4402
5063
|
witnessUtxo: {
|
|
4403
5064
|
amount: BigInt(input.value),
|
|
4404
|
-
script:
|
|
5065
|
+
script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
|
|
4405
5066
|
},
|
|
4406
5067
|
sighashType: btcSigner.SigHash.ALL_ANYONECANPAY,
|
|
4407
5068
|
tapLeafScript: [delegateTapLeaf]
|
|
@@ -4459,7 +5120,7 @@ async function makeSignedDelegateIntent(identity, coins, outputs, onchainOutputs
|
|
|
4459
5120
|
expire_at: 0,
|
|
4460
5121
|
cosigners_public_keys: cosignerPubKeys
|
|
4461
5122
|
};
|
|
4462
|
-
const proof =
|
|
5123
|
+
const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, outputs);
|
|
4463
5124
|
const signedProof = await identity.sign(proof);
|
|
4464
5125
|
return {
|
|
4465
5126
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -4477,10 +5138,10 @@ function getDayTimestamp(timestamp) {
|
|
|
4477
5138
|
function findDelegateTapLeaf(vtxo, delegatePubkey) {
|
|
4478
5139
|
if (!vtxo.tapTree) return void 0;
|
|
4479
5140
|
const pk = delegatePubkey.length === 66 ? delegatePubkey.slice(2) : delegatePubkey;
|
|
4480
|
-
const vtxoScript =
|
|
5141
|
+
const vtxoScript = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree);
|
|
4481
5142
|
return vtxoScript.leaves.find((tapLeaf) => {
|
|
4482
|
-
const arkTapscript =
|
|
4483
|
-
if (!
|
|
5143
|
+
const arkTapscript = chunkCMPJR3HS_cjs.decodeTapscript(chunkCMPJR3HS_cjs.scriptFromTapLeafScript(tapLeaf));
|
|
5144
|
+
if (!chunkCMPJR3HS_cjs.MultisigTapscript.is(arkTapscript)) return false;
|
|
4484
5145
|
return arkTapscript.params.pubkeys.map(base.hex.encode).includes(pk);
|
|
4485
5146
|
});
|
|
4486
5147
|
}
|
|
@@ -4684,7 +5345,7 @@ var InMemoryContractRepository = class {
|
|
|
4684
5345
|
}
|
|
4685
5346
|
};
|
|
4686
5347
|
function scriptFromArkAddress(address) {
|
|
4687
|
-
return base.hex.encode(
|
|
5348
|
+
return base.hex.encode(chunkCMPJR3HS_cjs.ArkAddress.decode(address).pkScript);
|
|
4688
5349
|
}
|
|
4689
5350
|
|
|
4690
5351
|
// src/repositories/indexedDB/schema.ts
|
|
@@ -5980,6 +6641,16 @@ function isDiscoverable(handler) {
|
|
|
5980
6641
|
}
|
|
5981
6642
|
|
|
5982
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
|
+
};
|
|
5983
6654
|
var ContractWatcher = class {
|
|
5984
6655
|
config;
|
|
5985
6656
|
contracts = /* @__PURE__ */ new Map();
|
|
@@ -5999,14 +6670,7 @@ var ContractWatcher = class {
|
|
|
5999
6670
|
*/
|
|
6000
6671
|
constructor(config) {
|
|
6001
6672
|
this.config = {
|
|
6002
|
-
|
|
6003
|
-
// 1 minute
|
|
6004
|
-
reconnectDelayMs: 1e3,
|
|
6005
|
-
// 1 second
|
|
6006
|
-
maxReconnectDelayMs: 3e4,
|
|
6007
|
-
// 30 seconds
|
|
6008
|
-
maxReconnectAttempts: 0,
|
|
6009
|
-
// unlimited
|
|
6673
|
+
...DEFAULT_CONTRACT_WATCHER_CONFIG,
|
|
6010
6674
|
...config
|
|
6011
6675
|
};
|
|
6012
6676
|
}
|
|
@@ -6199,7 +6863,7 @@ var ContractWatcher = class {
|
|
|
6199
6863
|
this.connectionState = "connected";
|
|
6200
6864
|
this.reconnectAttempts = 0;
|
|
6201
6865
|
this.listenLoop().catch((e) => {
|
|
6202
|
-
if (
|
|
6866
|
+
if (chunkX2EQLK4O_cjs.isEventSourceError(e)) {
|
|
6203
6867
|
console.debug("ContractWatcher subscription disconnected; reconnecting");
|
|
6204
6868
|
} else {
|
|
6205
6869
|
console.error(e);
|
|
@@ -6234,8 +6898,9 @@ var ContractWatcher = class {
|
|
|
6234
6898
|
}
|
|
6235
6899
|
this.connectionState = "reconnecting";
|
|
6236
6900
|
this.reconnectAttempts++;
|
|
6237
|
-
const delay =
|
|
6238
|
-
this.
|
|
6901
|
+
const delay = computeReconnectDelay(
|
|
6902
|
+
this.reconnectAttempts,
|
|
6903
|
+
this.config.reconnectDelayMs,
|
|
6239
6904
|
this.config.maxReconnectDelayMs
|
|
6240
6905
|
);
|
|
6241
6906
|
this.reconnectTimeoutId = setTimeout(() => {
|
|
@@ -6537,8 +7202,11 @@ function cursorCutoff(requestStartedAt) {
|
|
|
6537
7202
|
}
|
|
6538
7203
|
|
|
6539
7204
|
// src/contracts/contractManager.ts
|
|
6540
|
-
|
|
7205
|
+
function areCoalescibleContractTypes(a, b) {
|
|
7206
|
+
return a === "default" && b === "boarding" || a === "boarding" && b === "default";
|
|
7207
|
+
}
|
|
6541
7208
|
var SCAN_MAX_INDEX = 1e4;
|
|
7209
|
+
var DEFAULT_SCAN_BATCH = 10;
|
|
6542
7210
|
var ContractManager = class _ContractManager {
|
|
6543
7211
|
config;
|
|
6544
7212
|
watcher;
|
|
@@ -6639,7 +7307,7 @@ var ContractManager = class _ContractManager {
|
|
|
6639
7307
|
* `persisted` is `true`.
|
|
6640
7308
|
*/
|
|
6641
7309
|
async upsertContract(params) {
|
|
6642
|
-
const handler =
|
|
7310
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(params.type);
|
|
6643
7311
|
if (!handler) {
|
|
6644
7312
|
throw new Error(`No handler registered for contract type '${params.type}'`);
|
|
6645
7313
|
}
|
|
@@ -6662,8 +7330,11 @@ var ContractManager = class _ContractManager {
|
|
|
6662
7330
|
const [existing] = await this.getContracts({ script: params.script });
|
|
6663
7331
|
if (existing) {
|
|
6664
7332
|
if (existing.type === params.type) return { contract: existing, persisted: false };
|
|
7333
|
+
if (areCoalescibleContractTypes(existing.type, params.type)) {
|
|
7334
|
+
return { contract: existing, persisted: false };
|
|
7335
|
+
}
|
|
6665
7336
|
throw new Error(
|
|
6666
|
-
`Contract with script ${params.script} already exists with
|
|
7337
|
+
`Contract with script ${params.script} already exists with type ${existing.type}.`
|
|
6667
7338
|
);
|
|
6668
7339
|
}
|
|
6669
7340
|
const contract = {
|
|
@@ -6692,6 +7363,19 @@ var ContractManager = class _ContractManager {
|
|
|
6692
7363
|
* other handler hit it).
|
|
6693
7364
|
* - `persistAndWatchContract` rejecting is operational/fatal and
|
|
6694
7365
|
* propagates (only `discoverAt` is guarded).
|
|
7366
|
+
* - Within an index the handler probes run concurrently (independent
|
|
7367
|
+
* network reads); their hits are persisted sequentially in
|
|
7368
|
+
* `discoverables` order to preserve the first-wins collision tie-break.
|
|
7369
|
+
* - Indices are probed `batchSize` at a time (a second concurrency layer
|
|
7370
|
+
* over the per-index probes), but each window is CAPPED to
|
|
7371
|
+
* `gapLimit - unused` indices — the most a serial scan could still reach
|
|
7372
|
+
* before the gap window is guaranteed to close. So every index probed in
|
|
7373
|
+
* a window is one a one-index-at-a-time scan would also reach: nothing is
|
|
7374
|
+
* over-scanned, nothing is discarded, and `materialize`/`discoverAt` are
|
|
7375
|
+
* invoked on exactly the same index set. The window's hits are still
|
|
7376
|
+
* processed strictly in ascending index order, so the discovered set,
|
|
7377
|
+
* persisted rows, `lastIndexUsed`, and `handlerErrors` are byte-for-byte
|
|
7378
|
+
* identical to the serial path — only the wall-clock differs.
|
|
6695
7379
|
*/
|
|
6696
7380
|
async scanContracts(opts) {
|
|
6697
7381
|
const gapLimit = opts.gapLimit ?? 20;
|
|
@@ -6700,35 +7384,69 @@ var ContractManager = class _ContractManager {
|
|
|
6700
7384
|
`scanContracts: gapLimit must be a positive integer (got ${String(opts.gapLimit)})`
|
|
6701
7385
|
);
|
|
6702
7386
|
}
|
|
6703
|
-
const
|
|
7387
|
+
const batchSize = opts.batchSize ?? DEFAULT_SCAN_BATCH;
|
|
7388
|
+
if (!Number.isInteger(batchSize) || batchSize <= 0) {
|
|
7389
|
+
throw new Error(
|
|
7390
|
+
`scanContracts: batchSize must be a positive integer (got ${String(opts.batchSize)})`
|
|
7391
|
+
);
|
|
7392
|
+
}
|
|
7393
|
+
const registered = chunkGUTKJMSF_cjs.contractHandlers.getRegisteredTypes().map((t) => chunkGUTKJMSF_cjs.contractHandlers.get(t)).filter(isDiscoverable);
|
|
7394
|
+
const discoverables = [
|
|
7395
|
+
...registered.filter((h) => h.type === "boarding"),
|
|
7396
|
+
...registered.filter((h) => h.type !== "boarding")
|
|
7397
|
+
];
|
|
6704
7398
|
const maxIdx = opts.hd ? SCAN_MAX_INDEX : 0;
|
|
6705
7399
|
const handlerErrors = [];
|
|
6706
7400
|
let lastIndexUsed = -1;
|
|
6707
7401
|
let unused = 0;
|
|
6708
7402
|
let i = 0;
|
|
7403
|
+
const probeIndex = async (index) => {
|
|
7404
|
+
const descriptor = opts.materialize(index);
|
|
7405
|
+
return Promise.all(
|
|
7406
|
+
discoverables.map(async (h) => {
|
|
7407
|
+
try {
|
|
7408
|
+
return {
|
|
7409
|
+
ok: true,
|
|
7410
|
+
found: await h.discoverAt(index, descriptor, opts.deps)
|
|
7411
|
+
};
|
|
7412
|
+
} catch (error) {
|
|
7413
|
+
return { ok: false, error };
|
|
7414
|
+
}
|
|
7415
|
+
})
|
|
7416
|
+
);
|
|
7417
|
+
};
|
|
6709
7418
|
while (i <= maxIdx && unused < gapLimit) {
|
|
6710
|
-
const
|
|
6711
|
-
|
|
6712
|
-
for (
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
7419
|
+
const windowEnd = Math.min(maxIdx, i + Math.min(batchSize, gapLimit - unused) - 1);
|
|
7420
|
+
const windowIndices = [];
|
|
7421
|
+
for (let idx = i; idx <= windowEnd; idx++) windowIndices.push(idx);
|
|
7422
|
+
const windowProbes = await Promise.all(windowIndices.map(probeIndex));
|
|
7423
|
+
for (let w = 0; w < windowIndices.length; w++) {
|
|
7424
|
+
const index = windowIndices[w];
|
|
7425
|
+
const probes = windowProbes[w];
|
|
7426
|
+
let hitAtThisIndex = false;
|
|
7427
|
+
for (let h = 0; h < discoverables.length; h++) {
|
|
7428
|
+
const probe = probes[h];
|
|
7429
|
+
if (!probe.ok) {
|
|
7430
|
+
handlerErrors.push({
|
|
7431
|
+
handler: discoverables[h].type,
|
|
7432
|
+
index,
|
|
7433
|
+
error: probe.error
|
|
7434
|
+
});
|
|
7435
|
+
continue;
|
|
7436
|
+
}
|
|
7437
|
+
for (const c of probe.found) {
|
|
7438
|
+
await this.persistAndWatchContract(c);
|
|
7439
|
+
hitAtThisIndex = true;
|
|
7440
|
+
}
|
|
6719
7441
|
}
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
7442
|
+
if (hitAtThisIndex) {
|
|
7443
|
+
lastIndexUsed = index;
|
|
7444
|
+
unused = 0;
|
|
7445
|
+
} else {
|
|
7446
|
+
unused += 1;
|
|
6723
7447
|
}
|
|
6724
7448
|
}
|
|
6725
|
-
|
|
6726
|
-
lastIndexUsed = i;
|
|
6727
|
-
unused = 0;
|
|
6728
|
-
} else {
|
|
6729
|
-
unused += 1;
|
|
6730
|
-
}
|
|
6731
|
-
i += 1;
|
|
7449
|
+
i = windowEnd + 1;
|
|
6732
7450
|
}
|
|
6733
7451
|
if (opts.hd && i > maxIdx && unused < gapLimit) {
|
|
6734
7452
|
throw new Error(
|
|
@@ -6856,7 +7574,7 @@ var ContractManager = class _ContractManager {
|
|
|
6856
7574
|
const { contractScript, collaborative = true, walletPubKey, vtxo } = options;
|
|
6857
7575
|
const [contract] = await this.getContracts({ script: contractScript });
|
|
6858
7576
|
if (!contract) return [];
|
|
6859
|
-
const handler =
|
|
7577
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
|
|
6860
7578
|
if (!handler) return [];
|
|
6861
7579
|
const script = handler.createScript(contract.params);
|
|
6862
7580
|
const context = {
|
|
@@ -6876,7 +7594,7 @@ var ContractManager = class _ContractManager {
|
|
|
6876
7594
|
const { contractScript, collaborative = true, walletPubKey } = options;
|
|
6877
7595
|
const [contract] = await this.getContracts({ script: contractScript });
|
|
6878
7596
|
if (!contract) return [];
|
|
6879
|
-
const handler =
|
|
7597
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
|
|
6880
7598
|
if (!handler) return [];
|
|
6881
7599
|
const script = handler.createScript(contract.params);
|
|
6882
7600
|
const context = {
|
|
@@ -7110,7 +7828,7 @@ var ContractManager = class _ContractManager {
|
|
|
7110
7828
|
}
|
|
7111
7829
|
return result;
|
|
7112
7830
|
}
|
|
7113
|
-
async fetchContractVtxosBulk(contracts, pageSize = DEFAULT_PAGE_SIZE, syncWindow) {
|
|
7831
|
+
async fetchContractVtxosBulk(contracts, pageSize = chunkGUTKJMSF_cjs.DEFAULT_PAGE_SIZE, syncWindow) {
|
|
7114
7832
|
if (contracts.length === 0) {
|
|
7115
7833
|
return /* @__PURE__ */ new Map();
|
|
7116
7834
|
}
|
|
@@ -7286,7 +8004,7 @@ var HDDescriptorProvider = class _HDDescriptorProvider {
|
|
|
7286
8004
|
*/
|
|
7287
8005
|
materializeDescriptorAt(index) {
|
|
7288
8006
|
const descriptor = this.identity.descriptor;
|
|
7289
|
-
const network =
|
|
8007
|
+
const network = chunkGUTKJMSF_cjs.isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
|
|
7290
8008
|
const expansion = descriptorsScure.expand({ descriptor, network, index });
|
|
7291
8009
|
const keyInfo = expansion.expansionMap?.["@0"];
|
|
7292
8010
|
if (!keyInfo?.keyExpression) {
|
|
@@ -7429,12 +8147,13 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7429
8147
|
const provider = await resolveDescriptorProvider(config, setup.walletRepository);
|
|
7430
8148
|
if (!provider) return void 0;
|
|
7431
8149
|
const allowSilentFallback = (config.walletMode ?? "auto") === "auto";
|
|
7432
|
-
const expectedContractType = setup.offchainTapscript instanceof
|
|
8150
|
+
const expectedContractType = setup.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? "delegate" : "default";
|
|
7433
8151
|
const factoryOpts = {
|
|
7434
8152
|
walletRepository: setup.walletRepository,
|
|
7435
8153
|
contractRepository: setup.contractRepository,
|
|
7436
8154
|
serverPubKey: setup.serverPubKey,
|
|
7437
|
-
expectedContractType
|
|
8155
|
+
expectedContractType,
|
|
8156
|
+
baselineReceivePubKey: setup.offchainTapscript.options.pubKey
|
|
7438
8157
|
};
|
|
7439
8158
|
let boot;
|
|
7440
8159
|
try {
|
|
@@ -7479,14 +8198,17 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7479
8198
|
receivePubkey: existing.pubKey
|
|
7480
8199
|
};
|
|
7481
8200
|
}
|
|
7482
|
-
|
|
7483
|
-
if (
|
|
7484
|
-
descriptor = await provider.
|
|
8201
|
+
const current = hasPeekableDescriptor(provider) ? await provider.getCurrentSigningDescriptor() : void 0;
|
|
8202
|
+
if (current === void 0) {
|
|
8203
|
+
const descriptor = await provider.getNextSigningDescriptor();
|
|
8204
|
+
return {
|
|
8205
|
+
rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
|
|
8206
|
+
receivePubkey: deriveLeafPubkey(descriptor)
|
|
8207
|
+
};
|
|
7485
8208
|
}
|
|
7486
|
-
descriptor ??= await provider.getNextSigningDescriptor();
|
|
7487
8209
|
return {
|
|
7488
8210
|
rotator: new _WalletReceiveRotator(provider, void 0, opts.logger),
|
|
7489
|
-
receivePubkey: deriveLeafPubkey(
|
|
8211
|
+
receivePubkey: opts.baselineReceivePubKey ?? deriveLeafPubkey(current)
|
|
7490
8212
|
};
|
|
7491
8213
|
}
|
|
7492
8214
|
/**
|
|
@@ -7549,6 +8271,22 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7549
8271
|
async drain() {
|
|
7550
8272
|
await this.chain.catch(() => void 0);
|
|
7551
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
|
+
}
|
|
7552
8290
|
/**
|
|
7553
8291
|
* Tear down the subscription first so no late `vtxo_received` event
|
|
7554
8292
|
* can queue work on a disposing wallet, then drain any in-flight
|
|
@@ -7592,7 +8330,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7592
8330
|
const newAddress = newTapscript.address(wallet.network.hrp, wallet.arkServerPublicKey).encode();
|
|
7593
8331
|
const manager = await wallet.getContractManager();
|
|
7594
8332
|
const csvTimelock = newTapscript.options.csvTimelock;
|
|
7595
|
-
const csvTimelockStr =
|
|
8333
|
+
const csvTimelockStr = chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString();
|
|
7596
8334
|
const serverPubKeyHex = base.hex.encode(newTapscript.options.serverPubKey);
|
|
7597
8335
|
const baseParams = {
|
|
7598
8336
|
script: newScript,
|
|
@@ -7606,11 +8344,11 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7606
8344
|
// produce unsigned PSBTs that the server rejects with
|
|
7607
8345
|
// `INVALID_PSBT_INPUT (5): missing tapscript spend sig`.
|
|
7608
8346
|
metadata: {
|
|
7609
|
-
source:
|
|
8347
|
+
source: chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE,
|
|
7610
8348
|
signingDescriptor: descriptor
|
|
7611
8349
|
}
|
|
7612
8350
|
};
|
|
7613
|
-
if (newTapscript instanceof
|
|
8351
|
+
if (newTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
|
|
7614
8352
|
await manager.createContract({
|
|
7615
8353
|
...baseParams,
|
|
7616
8354
|
type: "delegate",
|
|
@@ -7642,7 +8380,7 @@ var WalletReceiveRotator = class _WalletReceiveRotator {
|
|
|
7642
8380
|
};
|
|
7643
8381
|
function deriveLeafPubkey(descriptor) {
|
|
7644
8382
|
try {
|
|
7645
|
-
return
|
|
8383
|
+
return chunkGUTKJMSF_cjs.deriveDescriptorLeafPubKey(descriptor);
|
|
7646
8384
|
} catch (e) {
|
|
7647
8385
|
throw new NonRangeableDescriptorError(
|
|
7648
8386
|
"Cannot derive leaf pubkey: descriptor is not a materialized, parsable tr(...) shape.",
|
|
@@ -7651,10 +8389,10 @@ function deriveLeafPubkey(descriptor) {
|
|
|
7651
8389
|
}
|
|
7652
8390
|
}
|
|
7653
8391
|
function rebuildTapscript(current, pubKey) {
|
|
7654
|
-
if (current instanceof
|
|
7655
|
-
return new
|
|
8392
|
+
if (current instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script) {
|
|
8393
|
+
return new chunkGUTKJMSF_cjs.DelegateVtxo.Script({ ...current.options, pubKey });
|
|
7656
8394
|
}
|
|
7657
|
-
return new
|
|
8395
|
+
return new chunkGUTKJMSF_cjs.DefaultVtxo.Script({ ...current.options, pubKey });
|
|
7658
8396
|
}
|
|
7659
8397
|
async function pickActiveReceive(contractRepository, serverPubKey, expectedType) {
|
|
7660
8398
|
const candidates = await contractRepository.getContracts({
|
|
@@ -7663,7 +8401,7 @@ async function pickActiveReceive(contractRepository, serverPubKey, expectedType)
|
|
|
7663
8401
|
});
|
|
7664
8402
|
const serverPubKeyHex = base.hex.encode(serverPubKey);
|
|
7665
8403
|
const matching = candidates.filter(
|
|
7666
|
-
(c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source ===
|
|
8404
|
+
(c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE
|
|
7667
8405
|
).sort((a, b) => {
|
|
7668
8406
|
if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt;
|
|
7669
8407
|
return signingDescriptorIndex(b.metadata?.signingDescriptor) - signingDescriptorIndex(a.metadata?.signingDescriptor);
|
|
@@ -7719,7 +8457,7 @@ var DescriptorSigningProviderMissingError = class extends Error {
|
|
|
7719
8457
|
};
|
|
7720
8458
|
|
|
7721
8459
|
// src/wallet/inputSignerRouter.ts
|
|
7722
|
-
var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate"]);
|
|
8460
|
+
var DESCRIPTOR_CAPABLE_CONTRACT_TYPES = /* @__PURE__ */ new Set(["default", "delegate", "boarding"]);
|
|
7723
8461
|
var InputSignerRouter = class {
|
|
7724
8462
|
constructor(deps) {
|
|
7725
8463
|
this.deps = deps;
|
|
@@ -7837,12 +8575,12 @@ var InputSignerRouter = class {
|
|
|
7837
8575
|
};
|
|
7838
8576
|
|
|
7839
8577
|
// src/wallet/wallet.ts
|
|
7840
|
-
var getArkadeServerUrl = ({ arkServerUrl }) => arkServerUrl ||
|
|
8578
|
+
var getArkadeServerUrl = ({ arkServerUrl }) => arkServerUrl || chunkCMPJR3HS_cjs.DEFAULT_ARKADE_SERVER_URL;
|
|
7841
8579
|
function intentProofJobs(coins) {
|
|
7842
8580
|
if (coins.length === 0) return [];
|
|
7843
8581
|
const coinJobs = coins.map((coin, i) => ({
|
|
7844
8582
|
index: i + 1,
|
|
7845
|
-
lookupScript:
|
|
8583
|
+
lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(coin.tapTree).pkScript
|
|
7846
8584
|
}));
|
|
7847
8585
|
return [{ index: 0, lookupScript: coinJobs[0].lookupScript }, ...coinJobs];
|
|
7848
8586
|
}
|
|
@@ -7851,6 +8589,11 @@ function extractArkProviderUrl(provider) {
|
|
|
7851
8589
|
return typeof serverUrl === "string" && serverUrl.length > 0 ? serverUrl : void 0;
|
|
7852
8590
|
}
|
|
7853
8591
|
var MAINNET_UNILATERAL_EXIT_DELAY = 605184n;
|
|
8592
|
+
function toXOnlyPubKey(pubkey) {
|
|
8593
|
+
if (pubkey.length === 33) return pubkey.slice(1);
|
|
8594
|
+
if (pubkey.length === 32) return pubkey;
|
|
8595
|
+
throw new Error(`invalid signer pubkey length: expected 32 or 33, got ${pubkey.length}`);
|
|
8596
|
+
}
|
|
7854
8597
|
function delayToTimelock(delay) {
|
|
7855
8598
|
return {
|
|
7856
8599
|
value: delay,
|
|
@@ -7861,27 +8604,37 @@ function dedupeTimelocks(timelocks) {
|
|
|
7861
8604
|
const seen = /* @__PURE__ */ new Set();
|
|
7862
8605
|
const deduped = [];
|
|
7863
8606
|
for (const timelock of timelocks) {
|
|
7864
|
-
const sequence =
|
|
8607
|
+
const sequence = chunkCMPJR3HS_cjs.timelockToSequence(timelock).toString();
|
|
7865
8608
|
if (seen.has(sequence)) continue;
|
|
7866
8609
|
seen.add(sequence);
|
|
7867
8610
|
deduped.push(timelock);
|
|
7868
8611
|
}
|
|
7869
8612
|
return deduped;
|
|
7870
8613
|
}
|
|
7871
|
-
function areSameScriptBaselineTypesCompatible(existingType, requestedType) {
|
|
7872
|
-
if (existingType === requestedType) return true;
|
|
7873
|
-
return existingType === "default" && requestedType === "boarding" || existingType === "boarding" && requestedType === "default";
|
|
7874
|
-
}
|
|
7875
8614
|
async function ensureWalletContract(manager, params) {
|
|
7876
|
-
const [existing] = await manager.getContracts({ script: params.script });
|
|
7877
|
-
if (existing && existing.type !== params.type && areSameScriptBaselineTypesCompatible(existing.type, params.type)) {
|
|
7878
|
-
if (params.type === "default" && existing.type === "boarding") {
|
|
7879
|
-
await manager.updateContract(params.script, { type: "default" });
|
|
7880
|
-
}
|
|
7881
|
-
return;
|
|
7882
|
-
}
|
|
7883
8615
|
await manager.createContract(params);
|
|
7884
8616
|
}
|
|
8617
|
+
async function resolveBoardingBootTapscript(contractRepository, serverPubKey, baseline) {
|
|
8618
|
+
const serverPubKeyHex = base.hex.encode(serverPubKey);
|
|
8619
|
+
const candidates = await contractRepository.getContracts({
|
|
8620
|
+
type: ["boarding"],
|
|
8621
|
+
state: "active"
|
|
8622
|
+
});
|
|
8623
|
+
const newest = candidates.filter(
|
|
8624
|
+
(c) => c.params.serverPubKey === serverPubKeyHex && c.metadata?.source === chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE
|
|
8625
|
+
).sort((a, b) => {
|
|
8626
|
+
if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt;
|
|
8627
|
+
return signingDescriptorIndex(b.metadata?.signingDescriptor) - signingDescriptorIndex(a.metadata?.signingDescriptor);
|
|
8628
|
+
})[0];
|
|
8629
|
+
if (!newest?.params.pubKey) return baseline;
|
|
8630
|
+
try {
|
|
8631
|
+
const pubKey = base.hex.decode(newest.params.pubKey);
|
|
8632
|
+
return new chunkGUTKJMSF_cjs.DefaultVtxo.Script({ ...baseline.options, pubKey });
|
|
8633
|
+
} catch (e) {
|
|
8634
|
+
console.warn("Skipping malformed boarding contract at boot", newest.script, e);
|
|
8635
|
+
return baseline;
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
7885
8638
|
function hasToReadonly(identity) {
|
|
7886
8639
|
return typeof identity === "object" && identity !== null && "toReadonly" in identity && typeof identity.toReadonly === "function";
|
|
7887
8640
|
}
|
|
@@ -7891,8 +8644,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7891
8644
|
this.network = network;
|
|
7892
8645
|
this.onchainProvider = onchainProvider;
|
|
7893
8646
|
this.indexerProvider = indexerProvider;
|
|
7894
|
-
this.arkServerPublicKey = arkServerPublicKey;
|
|
7895
|
-
this.boardingTapscript = boardingTapscript;
|
|
7896
8647
|
this.dustAmount = dustAmount;
|
|
7897
8648
|
this.walletRepository = walletRepository;
|
|
7898
8649
|
this.contractRepository = contractRepository;
|
|
@@ -7908,6 +8659,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7908
8659
|
}
|
|
7909
8660
|
}
|
|
7910
8661
|
this._offchainTapscript = offchainTapscript;
|
|
8662
|
+
this._boardingTapscript = boardingTapscript;
|
|
8663
|
+
this._arkServerPublicKey = arkServerPublicKey;
|
|
7911
8664
|
this.watcherConfig = watcherConfig;
|
|
7912
8665
|
this._assetManager = new ReadonlyAssetManager(this.indexerProvider);
|
|
7913
8666
|
this.walletContractTimelocks = walletContractTimelocks && walletContractTimelocks.length > 0 ? dedupeTimelocks(walletContractTimelocks) : [this.offchainTapscript.options.csvTimelock];
|
|
@@ -7916,7 +8669,6 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7916
8669
|
_contractManagerInitializing;
|
|
7917
8670
|
watcherConfig;
|
|
7918
8671
|
_assetManager;
|
|
7919
|
-
_syncVtxosInflight;
|
|
7920
8672
|
walletContractTimelocks;
|
|
7921
8673
|
// Outpoints ("txid:vout") committed to an in-flight settle/send. Filtered
|
|
7922
8674
|
// from getVtxos() so concurrent callers (UI, VtxoManager auto-renewal,
|
|
@@ -7934,6 +8686,70 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7934
8686
|
* {@link WalletReceiveRotator.rotate} is the sole intended caller of.
|
|
7935
8687
|
*/
|
|
7936
8688
|
_offchainTapscript;
|
|
8689
|
+
/**
|
|
8690
|
+
* Backing field for the current boarding tapscript (the QR / onboarding
|
|
8691
|
+
* target). Read via the public `boardingTapscript` getter; written only
|
|
8692
|
+
* by {@link Wallet.setBoardingTapscriptForRotation}, the sanctioned
|
|
8693
|
+
* boarding-rotation write path (analogue of `_offchainTapscript`). It is
|
|
8694
|
+
* a *current value*, not a fixed setup constant, because per-derivation
|
|
8695
|
+
* boarding rotation (plan §6-II) swaps it when a fresh boarding address
|
|
8696
|
+
* is explicitly allocated. Static / `auto` wallets never rotate it, so
|
|
8697
|
+
* it stays the index-0 baseline for their lifetime.
|
|
8698
|
+
*/
|
|
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
|
+
}
|
|
7937
8753
|
/**
|
|
7938
8754
|
* Currently-active receive tapscript. Read-only from the outside;
|
|
7939
8755
|
* mutated only via {@link Wallet.setOffchainTapscriptForRotation}
|
|
@@ -7943,15 +8759,71 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7943
8759
|
return this._offchainTapscript;
|
|
7944
8760
|
}
|
|
7945
8761
|
/**
|
|
7946
|
-
*
|
|
7947
|
-
*
|
|
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.
|
|
7948
8767
|
*/
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
8768
|
+
get arkServerPublicKey() {
|
|
8769
|
+
return this._arkServerPublicKey;
|
|
8770
|
+
}
|
|
8771
|
+
/**
|
|
8772
|
+
* The wallet's current boarding tapscript (the on-chain onboarding
|
|
8773
|
+
* target). Read-only from the outside; mutated only via
|
|
8774
|
+
* {@link Wallet.setBoardingTapscriptForRotation} when a fresh boarding
|
|
8775
|
+
* address is explicitly allocated. Single-valued for static / `auto`
|
|
8776
|
+
* wallets.
|
|
8777
|
+
*/
|
|
8778
|
+
get boardingTapscript() {
|
|
8779
|
+
return this._boardingTapscript;
|
|
8780
|
+
}
|
|
8781
|
+
/**
|
|
8782
|
+
* Listeners fired after the boarding tapscript rotates to a fresh index
|
|
8783
|
+
* (see {@link Wallet.setBoardingTapscriptForRotation}). A live
|
|
8784
|
+
* {@link notifyIncomingFunds} onchain watcher registers one so it can
|
|
8785
|
+
* re-subscribe to include the newly allocated boarding address within the
|
|
8786
|
+
* same session — without it, a deposit to the fresh address wouldn't fire
|
|
8787
|
+
* a notification until the watcher's next re-init. Always empty for
|
|
8788
|
+
* readonly / static / `auto` wallets, which never rotate boarding.
|
|
8789
|
+
*/
|
|
8790
|
+
_boardingRotationListeners = /* @__PURE__ */ new Set();
|
|
8791
|
+
/**
|
|
8792
|
+
* Register a listener invoked synchronously after each boarding rotation.
|
|
8793
|
+
* Returns an unsubscribe function. Protected: only internal subscribers
|
|
8794
|
+
* (the incoming-funds watcher) participate.
|
|
8795
|
+
*/
|
|
8796
|
+
onBoardingRotation(listener) {
|
|
8797
|
+
this._boardingRotationListeners.add(listener);
|
|
8798
|
+
return () => {
|
|
8799
|
+
this._boardingRotationListeners.delete(listener);
|
|
8800
|
+
};
|
|
8801
|
+
}
|
|
8802
|
+
/**
|
|
8803
|
+
* Notify boarding-rotation listeners. Called by the boarding-rotation
|
|
8804
|
+
* write path ({@link Wallet.setBoardingTapscriptForRotation}) once the new
|
|
8805
|
+
* tapscript is in place. A throwing listener is isolated so it can neither
|
|
8806
|
+
* break the rotation nor starve sibling listeners.
|
|
8807
|
+
*/
|
|
8808
|
+
notifyBoardingRotation() {
|
|
8809
|
+
for (const listener of this._boardingRotationListeners) {
|
|
8810
|
+
try {
|
|
8811
|
+
listener();
|
|
8812
|
+
} catch (e) {
|
|
8813
|
+
console.warn("Boarding-rotation listener failed", e);
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
/**
|
|
8818
|
+
* Protected helper to set up shared wallet configuration.
|
|
8819
|
+
* Extracts common logic used by both ReadonlyWallet.create() and Wallet.create().
|
|
8820
|
+
*/
|
|
8821
|
+
static async setupWalletConfig(config, pubKey) {
|
|
8822
|
+
const arkadeServerUrl = getArkadeServerUrl(config);
|
|
8823
|
+
const arkProvider = config.arkProvider || new chunkX2EQLK4O_cjs.RestArkProvider(arkadeServerUrl);
|
|
8824
|
+
let indexerProvider = config.indexerProvider;
|
|
8825
|
+
if (!indexerProvider) {
|
|
8826
|
+
let indexerUrl = config.indexerUrl;
|
|
7955
8827
|
if (!indexerUrl) {
|
|
7956
8828
|
if (config.arkProvider) {
|
|
7957
8829
|
const derived = extractArkProviderUrl(config.arkProvider);
|
|
@@ -7965,10 +8837,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
7965
8837
|
indexerUrl = arkadeServerUrl;
|
|
7966
8838
|
}
|
|
7967
8839
|
}
|
|
7968
|
-
indexerProvider = new
|
|
8840
|
+
indexerProvider = new chunkX2EQLK4O_cjs.RestIndexerProvider(indexerUrl);
|
|
7969
8841
|
}
|
|
7970
8842
|
const info = await arkProvider.getInfo();
|
|
7971
|
-
const network =
|
|
8843
|
+
const network = chunkCMPJR3HS_cjs.getNetwork(info.network);
|
|
7972
8844
|
if ("descriptor" in config.identity) {
|
|
7973
8845
|
const descriptor = config.identity.descriptor;
|
|
7974
8846
|
const identityIsMainnet = !descriptor.includes("tpub");
|
|
@@ -8015,11 +8887,11 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8015
8887
|
serverPubKey,
|
|
8016
8888
|
csvTimelock: exitTimelock
|
|
8017
8889
|
};
|
|
8018
|
-
const offchainTapscript = !delegatePubKey ? new
|
|
8019
|
-
const boardingTapscript =
|
|
8890
|
+
const offchainTapscript = !delegatePubKey ? new chunkGUTKJMSF_cjs.DefaultVtxo.Script(offchainOptions) : new chunkGUTKJMSF_cjs.DelegateVtxo.Script({ ...offchainOptions, delegatePubKey });
|
|
8891
|
+
const boardingTapscript = chunkGUTKJMSF_cjs.BoardingContractHandler.createScript({
|
|
8020
8892
|
pubKey: base.hex.encode(pubKey),
|
|
8021
8893
|
serverPubKey: base.hex.encode(serverPubKey),
|
|
8022
|
-
csvTimelock:
|
|
8894
|
+
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingTimelock).toString()
|
|
8023
8895
|
});
|
|
8024
8896
|
const walletRepository = config.storage?.walletRepository ?? new IndexedDBWalletRepository();
|
|
8025
8897
|
const contractRepository = config.storage?.contractRepository ?? new IndexedDBContractRepository();
|
|
@@ -8054,7 +8926,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8054
8926
|
throw new Error("Invalid configured public key");
|
|
8055
8927
|
}
|
|
8056
8928
|
const setup = await _ReadonlyWallet.setupWalletConfig(config, pubkey);
|
|
8057
|
-
|
|
8929
|
+
const wallet = new _ReadonlyWallet(
|
|
8058
8930
|
config.identity,
|
|
8059
8931
|
setup.network,
|
|
8060
8932
|
setup.onchainProvider,
|
|
@@ -8069,6 +8941,8 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8069
8941
|
config.watcherConfig,
|
|
8070
8942
|
setup.walletContractTimelocks
|
|
8071
8943
|
);
|
|
8944
|
+
wallet.refreshDeprecatedSigners(setup.info);
|
|
8945
|
+
return wallet;
|
|
8072
8946
|
}
|
|
8073
8947
|
get arkAddress() {
|
|
8074
8948
|
return this.offchainTapscript.address(this.network.hrp, this.arkServerPublicKey);
|
|
@@ -8092,10 +8966,12 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8092
8966
|
* Return the wallet's combined onchain and offchain balances.
|
|
8093
8967
|
*/
|
|
8094
8968
|
async getBalance() {
|
|
8095
|
-
const [boardingUtxos, vtxos] = await Promise.all([
|
|
8969
|
+
const [boardingUtxos, vtxos, pendingOutpoints] = await Promise.all([
|
|
8096
8970
|
this.getBoardingUtxos(),
|
|
8097
|
-
this.getVtxos()
|
|
8971
|
+
this.getVtxos(),
|
|
8972
|
+
this.pendingRecoveryOutpoints()
|
|
8098
8973
|
]);
|
|
8974
|
+
const isPendingRecovery = (coin) => pendingOutpoints.has(`${coin.txid}:${coin.vout}`);
|
|
8099
8975
|
let confirmed = 0;
|
|
8100
8976
|
let unconfirmed = 0;
|
|
8101
8977
|
for (const utxo of boardingUtxos) {
|
|
@@ -8108,11 +8984,15 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8108
8984
|
let settled = 0;
|
|
8109
8985
|
let preconfirmed = 0;
|
|
8110
8986
|
let recoverable = 0;
|
|
8111
|
-
|
|
8112
|
-
|
|
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);
|
|
8113
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);
|
|
8114
8994
|
const totalBoarding = confirmed + unconfirmed;
|
|
8115
|
-
const totalOffchain = settled + preconfirmed + recoverable;
|
|
8995
|
+
const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
|
|
8116
8996
|
const assetBalances = /* @__PURE__ */ new Map();
|
|
8117
8997
|
for (const vtxo of vtxos) {
|
|
8118
8998
|
if (!isSpendable(vtxo)) continue;
|
|
@@ -8137,6 +9017,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8137
9017
|
preconfirmed,
|
|
8138
9018
|
available: settled + preconfirmed,
|
|
8139
9019
|
recoverable,
|
|
9020
|
+
pendingRecovery,
|
|
8140
9021
|
total: totalBoarding + totalOffchain,
|
|
8141
9022
|
assets
|
|
8142
9023
|
};
|
|
@@ -8163,6 +9044,23 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8163
9044
|
return !!(f.withUnrolled && vtxo.isUnrolled);
|
|
8164
9045
|
});
|
|
8165
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
|
+
}
|
|
8166
9064
|
/**
|
|
8167
9065
|
* Return wallet transaction history derived from Arkade state and boarding transactions.
|
|
8168
9066
|
*/
|
|
@@ -8182,43 +9080,59 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8182
9080
|
await clearSyncCursor(this.walletRepository);
|
|
8183
9081
|
}
|
|
8184
9082
|
/**
|
|
8185
|
-
*
|
|
9083
|
+
* The on-chain (P2TR) addresses of every boarding tapscript this wallet
|
|
9084
|
+
* uses — the current address plus any historical rotated boarding
|
|
9085
|
+
* addresses. The aggregating boarding readers (history, notifications) fan
|
|
9086
|
+
* out over this set so deposits at previous boarding addresses are still
|
|
9087
|
+
* surfaced (plan §6-IV); {@link getBoardingAddress} stays single-valued.
|
|
9088
|
+
*/
|
|
9089
|
+
async getBoardingAddresses() {
|
|
9090
|
+
const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
|
|
9091
|
+
return tapscripts.map((t) => t.onchainAddress(this.network));
|
|
9092
|
+
}
|
|
9093
|
+
/**
|
|
9094
|
+
* Build a transaction history view across the wallet's boarding addresses
|
|
9095
|
+
* (current + historical rotated; plan §6-IV.1).
|
|
8186
9096
|
*/
|
|
8187
9097
|
async getBoardingTxs() {
|
|
8188
9098
|
const utxos = [];
|
|
8189
9099
|
const commitmentsToIgnore = /* @__PURE__ */ new Set();
|
|
8190
|
-
const
|
|
8191
|
-
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
9100
|
+
const tapscripts = await this.getBoardingTapscripts(this.watchedBoardingSigners());
|
|
8192
9101
|
const outspendCache = /* @__PURE__ */ new Map();
|
|
8193
|
-
for (const
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
9102
|
+
for (const tapscript of tapscripts) {
|
|
9103
|
+
const boardingAddress = tapscript.onchainAddress(this.network);
|
|
9104
|
+
const scriptHex = base.hex.encode(tapscript.pkScript);
|
|
9105
|
+
const txs = await this.onchainProvider.getTransactions(boardingAddress);
|
|
9106
|
+
for (const tx of txs) {
|
|
9107
|
+
for (let i = 0; i < tx.vout.length; i++) {
|
|
9108
|
+
const vout = tx.vout[i];
|
|
9109
|
+
if (vout.scriptpubkey_address === boardingAddress) {
|
|
9110
|
+
let spentStatuses = outspendCache.get(tx.txid);
|
|
9111
|
+
if (!spentStatuses) {
|
|
9112
|
+
spentStatuses = await this.onchainProvider.getTxOutspends(tx.txid);
|
|
9113
|
+
outspendCache.set(tx.txid, spentStatuses);
|
|
9114
|
+
}
|
|
9115
|
+
const spentStatus = spentStatuses[i];
|
|
9116
|
+
if (spentStatus?.spent) {
|
|
9117
|
+
commitmentsToIgnore.add(spentStatus.txid);
|
|
9118
|
+
}
|
|
9119
|
+
utxos.push({
|
|
9120
|
+
txid: tx.txid,
|
|
9121
|
+
vout: i,
|
|
9122
|
+
value: Number(vout.value),
|
|
9123
|
+
status: {
|
|
9124
|
+
confirmed: tx.status.confirmed,
|
|
9125
|
+
block_time: tx.status.block_time
|
|
9126
|
+
},
|
|
9127
|
+
isUnrolled: true,
|
|
9128
|
+
virtualStatus: {
|
|
9129
|
+
state: spentStatus?.spent ? "spent" : "settled",
|
|
9130
|
+
commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
|
|
9131
|
+
},
|
|
9132
|
+
createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
|
|
9133
|
+
script: scriptHex
|
|
9134
|
+
});
|
|
8205
9135
|
}
|
|
8206
|
-
utxos.push({
|
|
8207
|
-
txid: tx.txid,
|
|
8208
|
-
vout: i,
|
|
8209
|
-
value: Number(vout.value),
|
|
8210
|
-
status: {
|
|
8211
|
-
confirmed: tx.status.confirmed,
|
|
8212
|
-
block_time: tx.status.block_time
|
|
8213
|
-
},
|
|
8214
|
-
isUnrolled: true,
|
|
8215
|
-
virtualStatus: {
|
|
8216
|
-
state: spentStatus?.spent ? "spent" : "settled",
|
|
8217
|
-
commitmentTxIds: spentStatus?.spent ? [spentStatus.txid] : void 0
|
|
8218
|
-
},
|
|
8219
|
-
createdAt: tx.status.confirmed ? new Date(tx.status.block_time * 1e3) : /* @__PURE__ */ new Date(0),
|
|
8220
|
-
script: base.hex.encode(this.boardingTapscript.pkScript)
|
|
8221
|
-
});
|
|
8222
9136
|
}
|
|
8223
9137
|
}
|
|
8224
9138
|
}
|
|
@@ -8248,48 +9162,176 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8248
9162
|
};
|
|
8249
9163
|
}
|
|
8250
9164
|
/**
|
|
8251
|
-
*
|
|
9165
|
+
* The set of boarding tapscripts whose on-chain UTXOs belong to this
|
|
9166
|
+
* wallet — the current display tapscript plus every historical boarding
|
|
9167
|
+
* address it has used. Under per-derivation rotation (plan §6-II) a wallet
|
|
9168
|
+
* can hold unspent boarding UTXOs at several addresses at once, so fund
|
|
9169
|
+
* discovery / spending must enumerate them all, not just the current one
|
|
9170
|
+
* (plan §6-III.1). Deduplicated by scriptPubKey.
|
|
9171
|
+
*
|
|
9172
|
+
* Always includes the index-0 baseline (identity x-only key), which covers
|
|
9173
|
+
* the degenerate equal-delay case where the index-0 boarding row is
|
|
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.
|
|
8252
9182
|
*/
|
|
8253
|
-
async
|
|
8254
|
-
const
|
|
8255
|
-
const
|
|
8256
|
-
const
|
|
8257
|
-
|
|
9183
|
+
async getBoardingTapscripts(allowedSigners) {
|
|
9184
|
+
const byScript = /* @__PURE__ */ new Map();
|
|
9185
|
+
const add = (s) => byScript.set(base.hex.encode(s.pkScript), s);
|
|
9186
|
+
const boardingCsv = this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
9187
|
+
add(
|
|
9188
|
+
new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
9189
|
+
pubKey: await this.identity.xOnlyPublicKey(),
|
|
9190
|
+
serverPubKey: this.boardingTapscript.options.serverPubKey,
|
|
9191
|
+
csvTimelock: boardingCsv
|
|
9192
|
+
})
|
|
9193
|
+
);
|
|
9194
|
+
add(this.boardingTapscript);
|
|
9195
|
+
const serverPubKeyHex = base.hex.encode(this.boardingTapscript.options.serverPubKey);
|
|
9196
|
+
const allowed = allowedSigners ?? /* @__PURE__ */ new Set([toXOnlySignerHex(serverPubKeyHex)]);
|
|
9197
|
+
const boardingContracts = await this.contractRepository.getContracts({
|
|
9198
|
+
type: ["boarding"]
|
|
8258
9199
|
});
|
|
8259
|
-
|
|
8260
|
-
|
|
9200
|
+
for (const c of boardingContracts) {
|
|
9201
|
+
if (!allowed.has(toXOnlySignerHex(c.params.serverPubKey))) continue;
|
|
9202
|
+
try {
|
|
9203
|
+
add(chunkGUTKJMSF_cjs.BoardingContractHandler.createScript(c.params));
|
|
9204
|
+
} catch (e) {
|
|
9205
|
+
console.warn("Skipping malformed boarding contract", c.script, e);
|
|
9206
|
+
}
|
|
9207
|
+
}
|
|
9208
|
+
return [...byScript.values()];
|
|
9209
|
+
}
|
|
9210
|
+
/**
|
|
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}).
|
|
9224
|
+
*/
|
|
9225
|
+
async getBoardingUtxosForSigners(allowedSigners) {
|
|
9226
|
+
const tapscripts = await this.getBoardingTapscripts(allowedSigners);
|
|
9227
|
+
const groups = [];
|
|
9228
|
+
for (const tapscript of tapscripts) {
|
|
9229
|
+
const address = tapscript.onchainAddress(this.network);
|
|
9230
|
+
const coins = await this.onchainProvider.getCoins(address);
|
|
9231
|
+
const utxos = coins.map((utxo) => extendCoinWithTapscript(tapscript, utxo));
|
|
9232
|
+
await this.walletRepository.saveUtxos(address, utxos);
|
|
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
|
+
});
|
|
9244
|
+
}
|
|
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);
|
|
8261
9266
|
}
|
|
8262
9267
|
/**
|
|
8263
9268
|
* Subscribe to onchain and offchain notifications for newly received funds.
|
|
8264
9269
|
*
|
|
9270
|
+
* The onchain watcher tracks the full boarding-address set (current +
|
|
9271
|
+
* historical rotated). When boarding rotates *after* subscribing — e.g.
|
|
9272
|
+
* rotate-on-board allocates a fresh address via
|
|
9273
|
+
* {@link getNewBoardingAddress} — the watcher automatically re-subscribes
|
|
9274
|
+
* to widen its set, so a deposit to the new address fires a notification
|
|
9275
|
+
* within the same session (no watcher re-init required). The re-subscribe
|
|
9276
|
+
* is driven by {@link onBoardingRotation}; static / `auto` / readonly
|
|
9277
|
+
* wallets never rotate boarding, so it never fires for them.
|
|
9278
|
+
*
|
|
8265
9279
|
* @param eventCallback - Callback invoked when matching funds are detected
|
|
8266
9280
|
* @returns A function that stops the subscriptions
|
|
8267
9281
|
*/
|
|
8268
9282
|
async notifyIncomingFunds(eventCallback) {
|
|
8269
9283
|
const arkAddress = await this.getAddress();
|
|
8270
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
8271
9284
|
let onchainStopFunc;
|
|
8272
9285
|
let indexerStopFunc;
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
(
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
9286
|
+
let boardingRotationStopFunc;
|
|
9287
|
+
let stopped = false;
|
|
9288
|
+
let onchainChain = Promise.resolve();
|
|
9289
|
+
const subscribeOnchain = () => {
|
|
9290
|
+
onchainChain = onchainChain.then(async () => {
|
|
9291
|
+
if (stopped || !this.onchainProvider) return;
|
|
9292
|
+
const boardingAddresses = await this.getBoardingAddresses();
|
|
9293
|
+
if (boardingAddresses.length === 0) return;
|
|
9294
|
+
const boardingAddressSet = new Set(boardingAddresses);
|
|
9295
|
+
const previousStop = onchainStopFunc;
|
|
9296
|
+
const stop = await this.onchainProvider.watchAddresses(
|
|
9297
|
+
boardingAddresses,
|
|
9298
|
+
(txs) => {
|
|
9299
|
+
const coins = txs.flatMap((tx) => {
|
|
9300
|
+
const { txid, status } = tx;
|
|
9301
|
+
const matched = [];
|
|
9302
|
+
tx.vout.forEach((v, vout) => {
|
|
9303
|
+
if (boardingAddressSet.has(v.scriptpubkey_address)) {
|
|
9304
|
+
matched.push({
|
|
9305
|
+
txid,
|
|
9306
|
+
vout,
|
|
9307
|
+
value: Number(v.value),
|
|
9308
|
+
status
|
|
9309
|
+
});
|
|
9310
|
+
}
|
|
9311
|
+
});
|
|
9312
|
+
return matched;
|
|
9313
|
+
});
|
|
9314
|
+
eventCallback({
|
|
9315
|
+
type: "utxo",
|
|
9316
|
+
coins
|
|
9317
|
+
});
|
|
9318
|
+
}
|
|
9319
|
+
);
|
|
9320
|
+
if (stopped) {
|
|
9321
|
+
stop();
|
|
9322
|
+
return;
|
|
8290
9323
|
}
|
|
8291
|
-
|
|
8292
|
-
|
|
9324
|
+
onchainStopFunc = stop;
|
|
9325
|
+
previousStop?.();
|
|
9326
|
+
}).catch((e) => {
|
|
9327
|
+
console.warn("Failed to (re)subscribe boarding-funds watcher", e);
|
|
9328
|
+
});
|
|
9329
|
+
return onchainChain;
|
|
9330
|
+
};
|
|
9331
|
+
boardingRotationStopFunc = this.onBoardingRotation(() => {
|
|
9332
|
+
void subscribeOnchain();
|
|
9333
|
+
});
|
|
9334
|
+
await subscribeOnchain();
|
|
8293
9335
|
if (this.indexerProvider && arkAddress) {
|
|
8294
9336
|
const cm = await this.getContractManager();
|
|
8295
9337
|
let annotationQueue = Promise.resolve();
|
|
@@ -8318,7 +9360,10 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8318
9360
|
});
|
|
8319
9361
|
}
|
|
8320
9362
|
const stopFunc = () => {
|
|
9363
|
+
stopped = true;
|
|
9364
|
+
boardingRotationStopFunc?.();
|
|
8321
9365
|
onchainStopFunc?.();
|
|
9366
|
+
onchainStopFunc = void 0;
|
|
8322
9367
|
indexerStopFunc?.();
|
|
8323
9368
|
};
|
|
8324
9369
|
return stopFunc;
|
|
@@ -8359,7 +9404,7 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8359
9404
|
});
|
|
8360
9405
|
for (const contract of contracts) {
|
|
8361
9406
|
if (map.has(contract.script)) continue;
|
|
8362
|
-
const handler =
|
|
9407
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(contract.type);
|
|
8363
9408
|
if (handler) {
|
|
8364
9409
|
const script = handler.createScript(contract.params);
|
|
8365
9410
|
map.set(contract.script, script);
|
|
@@ -8424,60 +9469,82 @@ var ReadonlyWallet = class _ReadonlyWallet {
|
|
|
8424
9469
|
watcherConfig: this.watcherConfig
|
|
8425
9470
|
});
|
|
8426
9471
|
const baselinePubkey = await this.identity.xOnlyPublicKey();
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
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({
|
|
8430
9529
|
pubKey: baselinePubkey,
|
|
8431
|
-
serverPubKey
|
|
8432
|
-
csvTimelock
|
|
9530
|
+
serverPubKey,
|
|
9531
|
+
csvTimelock: boardingCsvTimelock
|
|
8433
9532
|
});
|
|
8434
|
-
const
|
|
9533
|
+
const boardingScriptHex = base.hex.encode(baselineBoarding.pkScript);
|
|
9534
|
+
if (seenBaselineScripts.has(boardingScriptHex)) continue;
|
|
9535
|
+
seenBaselineScripts.add(boardingScriptHex);
|
|
8435
9536
|
await ensureWalletContract(manager, {
|
|
8436
|
-
type: "
|
|
9537
|
+
type: "boarding",
|
|
8437
9538
|
params: {
|
|
8438
|
-
pubKey: base.hex.encode(
|
|
8439
|
-
serverPubKey: base.hex.encode(
|
|
8440
|
-
csvTimelock:
|
|
9539
|
+
pubKey: base.hex.encode(baselineBoarding.options.pubKey),
|
|
9540
|
+
serverPubKey: base.hex.encode(serverPubKey),
|
|
9541
|
+
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(boardingCsvTimelock).toString()
|
|
8441
9542
|
},
|
|
8442
|
-
script:
|
|
8443
|
-
address:
|
|
9543
|
+
script: boardingScriptHex,
|
|
9544
|
+
address: baselineBoarding.address(this.network.hrp, serverPubKey).encode(),
|
|
8444
9545
|
state: "active"
|
|
8445
9546
|
});
|
|
8446
|
-
if (this.offchainTapscript instanceof chunkGYSK5R57_cjs.DelegateVtxo.Script) {
|
|
8447
|
-
const delegateScript = new chunkGYSK5R57_cjs.DelegateVtxo.Script({
|
|
8448
|
-
pubKey: baselinePubkey,
|
|
8449
|
-
serverPubKey: this.offchainTapscript.options.serverPubKey,
|
|
8450
|
-
delegatePubKey: this.offchainTapscript.options.delegatePubKey,
|
|
8451
|
-
csvTimelock
|
|
8452
|
-
});
|
|
8453
|
-
const delegateScriptHex = base.hex.encode(delegateScript.pkScript);
|
|
8454
|
-
await manager.createContract({
|
|
8455
|
-
type: "delegate",
|
|
8456
|
-
params: {
|
|
8457
|
-
pubKey: base.hex.encode(delegateScript.options.pubKey),
|
|
8458
|
-
serverPubKey: base.hex.encode(delegateScript.options.serverPubKey),
|
|
8459
|
-
delegatePubKey: base.hex.encode(delegateScript.options.delegatePubKey),
|
|
8460
|
-
csvTimelock: csvTimelockStr
|
|
8461
|
-
},
|
|
8462
|
-
script: delegateScriptHex,
|
|
8463
|
-
address: delegateScript.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8464
|
-
state: "active"
|
|
8465
|
-
});
|
|
8466
|
-
}
|
|
8467
9547
|
}
|
|
8468
|
-
const boardingScriptHex = base.hex.encode(this.boardingTapscript.pkScript);
|
|
8469
|
-
const boardingCsvTimelock = this.boardingTapscript.options.csvTimelock ?? chunkGYSK5R57_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
8470
|
-
await ensureWalletContract(manager, {
|
|
8471
|
-
type: "boarding",
|
|
8472
|
-
params: {
|
|
8473
|
-
pubKey: base.hex.encode(this.boardingTapscript.options.pubKey),
|
|
8474
|
-
serverPubKey: base.hex.encode(this.boardingTapscript.options.serverPubKey),
|
|
8475
|
-
csvTimelock: chunkWMIPYZSB_cjs.timelockToSequence(boardingCsvTimelock).toString()
|
|
8476
|
-
},
|
|
8477
|
-
script: boardingScriptHex,
|
|
8478
|
-
address: this.boardingTapscript.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
8479
|
-
state: "active"
|
|
8480
|
-
});
|
|
8481
9548
|
return manager;
|
|
8482
9549
|
}
|
|
8483
9550
|
/** Dispose wallet-owned managers and release background resources. */
|
|
@@ -8510,7 +9577,6 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8510
9577
|
walletContractTimelocks
|
|
8511
9578
|
);
|
|
8512
9579
|
this.arkProvider = arkProvider;
|
|
8513
|
-
this.serverUnrollScript = serverUnrollScript;
|
|
8514
9580
|
this.forfeitOutputScript = forfeitOutputScript;
|
|
8515
9581
|
this.forfeitPubkey = forfeitPubkey;
|
|
8516
9582
|
this.identity = identity;
|
|
@@ -8531,6 +9597,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8531
9597
|
this.settlementConfig = { ...DEFAULT_SETTLEMENT_CONFIG };
|
|
8532
9598
|
}
|
|
8533
9599
|
this._delegateManager = delegateProvider ? new DelegateManagerImpl(delegateProvider, arkProvider, identity) : void 0;
|
|
9600
|
+
this._serverUnrollScript = serverUnrollScript;
|
|
8534
9601
|
this._receiveRotator = receiveRotator;
|
|
8535
9602
|
this._descriptorProvider = descriptorProvider;
|
|
8536
9603
|
this._signerRouter = new InputSignerRouter({
|
|
@@ -8556,6 +9623,43 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8556
9623
|
* the contract manager is up first.
|
|
8557
9624
|
*/
|
|
8558
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
|
+
}
|
|
8559
9663
|
_receiveRotatorInstalled = false;
|
|
8560
9664
|
/**
|
|
8561
9665
|
* Descriptor-aware signer used by {@link _signerRouter} to sign
|
|
@@ -8575,6 +9679,230 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8575
9679
|
setOffchainTapscriptForRotation(tapscript) {
|
|
8576
9680
|
this._offchainTapscript = tapscript;
|
|
8577
9681
|
}
|
|
9682
|
+
/**
|
|
9683
|
+
* @internal Sole write path for `boardingTapscript` after construction.
|
|
9684
|
+
* Called by {@link Wallet.getNewBoardingAddress} once the rotated
|
|
9685
|
+
* boarding contract has been persisted. External code must treat
|
|
9686
|
+
* `boardingTapscript` as read-only.
|
|
9687
|
+
*/
|
|
9688
|
+
setBoardingTapscriptForRotation(tapscript) {
|
|
9689
|
+
this._boardingTapscript = tapscript;
|
|
9690
|
+
this.notifyBoardingRotation();
|
|
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();
|
|
9730
|
+
/**
|
|
9731
|
+
* Allocate and return a *fresh* on-chain boarding address, rotating the
|
|
9732
|
+
* wallet's current boarding tapscript to a new HD index.
|
|
9733
|
+
*
|
|
9734
|
+
* This is the explicit boarding allocator — the analogue of dotnet's
|
|
9735
|
+
* `GetNextContract(NextContractPurpose.Boarding)`. Unlike
|
|
9736
|
+
* {@link getBoardingAddress} (a stable read of the current display
|
|
9737
|
+
* address that never burns an index), each call here:
|
|
9738
|
+
*
|
|
9739
|
+
* - allocates the next index from the shared HD stream (so boarding and
|
|
9740
|
+
* L2 receive interleave on one monotonic index);
|
|
9741
|
+
* - builds the boarding tapscript at that index with the boarding-exit
|
|
9742
|
+
* CSV;
|
|
9743
|
+
* - persists an `active` `boarding` contract tagged
|
|
9744
|
+
* {@link WALLET_RECEIVE_SOURCE} (with its `signingDescriptor`) so the
|
|
9745
|
+
* ContractWatcher monitors it, boot can restore it as the current
|
|
9746
|
+
* boarding address, and descriptor-aware signing can recover the
|
|
9747
|
+
* per-index key;
|
|
9748
|
+
* - swaps the wallet's current `boardingTapscript`.
|
|
9749
|
+
*
|
|
9750
|
+
* Gated by `walletMode`: a static / `auto` wallet has no descriptor
|
|
9751
|
+
* provider and keeps a single index-0 boarding address for its lifetime,
|
|
9752
|
+
* so this returns the existing {@link getBoardingAddress} unchanged
|
|
9753
|
+
* (no rotation, no index burned).
|
|
9754
|
+
*/
|
|
9755
|
+
async getNewBoardingAddress() {
|
|
9756
|
+
const provider = this._descriptorProvider;
|
|
9757
|
+
if (!provider) {
|
|
9758
|
+
return this.getBoardingAddress();
|
|
9759
|
+
}
|
|
9760
|
+
const descriptor = await provider.getNextSigningDescriptor();
|
|
9761
|
+
const pubKey = chunkGUTKJMSF_cjs.deriveDescriptorLeafPubKey(descriptor);
|
|
9762
|
+
const newBoarding = new chunkGUTKJMSF_cjs.DefaultVtxo.Script({
|
|
9763
|
+
...this._boardingTapscript.options,
|
|
9764
|
+
pubKey
|
|
9765
|
+
});
|
|
9766
|
+
const csvTimelock = newBoarding.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK;
|
|
9767
|
+
const manager = await this.getContractManager();
|
|
9768
|
+
await manager.createContract({
|
|
9769
|
+
type: "boarding",
|
|
9770
|
+
params: {
|
|
9771
|
+
pubKey: base.hex.encode(pubKey),
|
|
9772
|
+
serverPubKey: base.hex.encode(newBoarding.options.serverPubKey),
|
|
9773
|
+
csvTimelock: chunkCMPJR3HS_cjs.timelockToSequence(csvTimelock).toString()
|
|
9774
|
+
},
|
|
9775
|
+
script: base.hex.encode(newBoarding.pkScript),
|
|
9776
|
+
address: newBoarding.address(this.network.hrp, this.arkServerPublicKey).encode(),
|
|
9777
|
+
state: "active",
|
|
9778
|
+
metadata: {
|
|
9779
|
+
source: chunkGUTKJMSF_cjs.WALLET_RECEIVE_SOURCE,
|
|
9780
|
+
signingDescriptor: descriptor
|
|
9781
|
+
}
|
|
9782
|
+
});
|
|
9783
|
+
this.setBoardingTapscriptForRotation(newBoarding);
|
|
9784
|
+
return newBoarding.onchainAddress(this.network);
|
|
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
|
+
}
|
|
8578
9906
|
/**
|
|
8579
9907
|
* Async mutex that serializes all operations submitting VTXOs to the Arkade
|
|
8580
9908
|
* server (`settle`, `send`, `sendBitcoin`). This prevents VtxoManager's
|
|
@@ -8658,13 +9986,26 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8658
9986
|
const hd = provider instanceof HDDescriptorProvider;
|
|
8659
9987
|
const staticDescriptor = hd ? void 0 : `tr(${base.hex.encode(await this.identity.xOnlyPublicKey())})`;
|
|
8660
9988
|
const materialize = (index) => hd ? provider.materializeDescriptorAt(index) : staticDescriptor;
|
|
8661
|
-
const delegatePubKey = this.offchainTapscript instanceof
|
|
9989
|
+
const delegatePubKey = this.offchainTapscript instanceof chunkGUTKJMSF_cjs.DelegateVtxo.Script ? this.offchainTapscript.options.delegatePubKey : void 0;
|
|
9990
|
+
const arkInfo = await this.arkProvider.getInfo();
|
|
9991
|
+
const currentSignerPubKey = toXOnlyPubKey(base.hex.decode(arkInfo.signerPubkey));
|
|
9992
|
+
const deprecatedSignerPubKeys = arkInfo.deprecatedSigners.map(
|
|
9993
|
+
(s) => toXOnlyPubKey(base.hex.decode(s.pubkey))
|
|
9994
|
+
);
|
|
8662
9995
|
const deps = {
|
|
8663
9996
|
indexerProvider: this.indexerProvider,
|
|
8664
9997
|
onchainProvider: this.onchainProvider,
|
|
8665
9998
|
network: { hrp: this.network.hrp },
|
|
8666
|
-
|
|
9999
|
+
// Full network for the boarding on-chain (P2TR) probe — the
|
|
10000
|
+
// `{ hrp }` shape above lacks the `bech32` data
|
|
10001
|
+
// `VtxoScript.onchainAddress` needs (plan §6-I.1).
|
|
10002
|
+
onchainNetwork: this.network,
|
|
10003
|
+
serverPubKey: currentSignerPubKey,
|
|
10004
|
+
deprecatedSignerPubKeys,
|
|
8667
10005
|
csvTimelocks: this.walletContractTimelocks,
|
|
10006
|
+
// Boarding-exit CSV so the boarding handler can build its
|
|
10007
|
+
// candidate script (distinct from the unilateral-exit matrix).
|
|
10008
|
+
boardingTimelock: this.boardingTapscript.options.csvTimelock ?? chunkGUTKJMSF_cjs.DefaultVtxo.Script.DEFAULT_TIMELOCK,
|
|
8668
10009
|
delegatePubKey
|
|
8669
10010
|
};
|
|
8670
10011
|
const result = await manager.scanContracts({
|
|
@@ -8722,6 +10063,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8722
10063
|
}
|
|
8723
10064
|
async dispose() {
|
|
8724
10065
|
await this._restoreInFlight?.catch(() => void 0);
|
|
10066
|
+
this._serverInfoUnsub?.();
|
|
10067
|
+
this._serverInfoUnsub = void 0;
|
|
10068
|
+
await this._serverInfoInFlight?.catch(() => void 0);
|
|
8725
10069
|
let rotatorError;
|
|
8726
10070
|
try {
|
|
8727
10071
|
await this._receiveRotator?.dispose();
|
|
@@ -8765,7 +10109,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8765
10109
|
let serverUnrollScript;
|
|
8766
10110
|
try {
|
|
8767
10111
|
const raw = base.hex.decode(setup.info.checkpointTapscript);
|
|
8768
|
-
serverUnrollScript =
|
|
10112
|
+
serverUnrollScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(raw);
|
|
8769
10113
|
} catch (e) {
|
|
8770
10114
|
throw new Error("Invalid checkpointTapscript from server");
|
|
8771
10115
|
}
|
|
@@ -8796,6 +10140,25 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8796
10140
|
boot?.rotator,
|
|
8797
10141
|
boot?.provider
|
|
8798
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
|
+
}
|
|
10152
|
+
if (boot?.provider) {
|
|
10153
|
+
const resolvedBoarding = await resolveBoardingBootTapscript(
|
|
10154
|
+
setup.contractRepository,
|
|
10155
|
+
setup.serverPubKey,
|
|
10156
|
+
setup.boardingTapscript
|
|
10157
|
+
);
|
|
10158
|
+
if (resolvedBoarding !== setup.boardingTapscript) {
|
|
10159
|
+
wallet.setBoardingTapscriptForRotation(resolvedBoarding);
|
|
10160
|
+
}
|
|
10161
|
+
}
|
|
8799
10162
|
await wallet.getVtxoManager();
|
|
8800
10163
|
return wallet;
|
|
8801
10164
|
}
|
|
@@ -8818,7 +10181,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8818
10181
|
*/
|
|
8819
10182
|
async toReadonly() {
|
|
8820
10183
|
const readonlyIdentity = hasToReadonly(this.identity) ? await this.identity.toReadonly() : this.identity;
|
|
8821
|
-
|
|
10184
|
+
const readonly = new ReadonlyWallet(
|
|
8822
10185
|
readonlyIdentity,
|
|
8823
10186
|
this.network,
|
|
8824
10187
|
this.onchainProvider,
|
|
@@ -8833,6 +10196,8 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8833
10196
|
this.watcherConfig,
|
|
8834
10197
|
this.walletContractTimelocks
|
|
8835
10198
|
);
|
|
10199
|
+
readonly._deprecatedSigners = new Map(this._deprecatedSigners);
|
|
10200
|
+
return readonly;
|
|
8836
10201
|
}
|
|
8837
10202
|
/** Returns the delegate manager when delegation support is configured. */
|
|
8838
10203
|
async getDelegateManager() {
|
|
@@ -8858,10 +10223,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8858
10223
|
if (params.selectedVtxos && params.selectedVtxos.length > 0) {
|
|
8859
10224
|
return this._withTxLock(async () => {
|
|
8860
10225
|
const offchainTapscript = this.offchainTapscript;
|
|
8861
|
-
const
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
);
|
|
10226
|
+
const serverPubKey = this.arkServerPublicKey;
|
|
10227
|
+
const serverUnrollScript = this.serverUnrollScript;
|
|
10228
|
+
const arkAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
|
|
8865
10229
|
const selectedVtxoSum = params.selectedVtxos.map((v) => v.value).reduce((a, b) => a + b, 0);
|
|
8866
10230
|
if (selectedVtxoSum < params.amount) {
|
|
8867
10231
|
throw new Error("Selected VTXOs do not cover specified amount");
|
|
@@ -8871,7 +10235,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8871
10235
|
inputs: params.selectedVtxos,
|
|
8872
10236
|
changeAmount: BigInt(changeAmount)
|
|
8873
10237
|
};
|
|
8874
|
-
const outputAddress =
|
|
10238
|
+
const outputAddress = chunkCMPJR3HS_cjs.ArkAddress.decode(params.address);
|
|
8875
10239
|
const outputScript = BigInt(params.amount) < this.dustAmount ? outputAddress.subdustPkScript : outputAddress.pkScript;
|
|
8876
10240
|
const outputs = [
|
|
8877
10241
|
{
|
|
@@ -8886,25 +10250,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8886
10250
|
amount: BigInt(selected.changeAmount)
|
|
8887
10251
|
});
|
|
8888
10252
|
}
|
|
8889
|
-
this.
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
arkTxid,
|
|
8898
|
-
signedCheckpointTxs,
|
|
8899
|
-
params.amount,
|
|
8900
|
-
selected.changeAmount,
|
|
8901
|
-
selected.changeAmount > 0n ? outputs.length - 1 : 0,
|
|
8902
|
-
offchainTapscript
|
|
8903
|
-
);
|
|
8904
|
-
return arkTxid;
|
|
8905
|
-
} finally {
|
|
8906
|
-
this._removePendingSpends(selected.inputs);
|
|
8907
|
-
}
|
|
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
|
+
});
|
|
8908
10261
|
});
|
|
8909
10262
|
}
|
|
8910
10263
|
return this.send({
|
|
@@ -8934,11 +10287,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8934
10287
|
}
|
|
8935
10288
|
}
|
|
8936
10289
|
}
|
|
10290
|
+
const offchainAddress = await this.getAddress();
|
|
10291
|
+
const offchainPkScript = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress).pkScript;
|
|
10292
|
+
const offchainOutputScript = base.hex.encode(offchainPkScript);
|
|
8937
10293
|
if (!params) {
|
|
8938
|
-
const { fees } = await this.arkProvider.getInfo();
|
|
10294
|
+
const { fees, vtxoMaxAmount } = await this.arkProvider.getInfo();
|
|
8939
10295
|
const estimator = new Estimator(fees.intentFee);
|
|
8940
10296
|
let amount = 0;
|
|
8941
|
-
const exitScript =
|
|
10297
|
+
const exitScript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.decode(
|
|
8942
10298
|
base.hex.decode(this.boardingTapscript.exitScript)
|
|
8943
10299
|
);
|
|
8944
10300
|
const boardingTimelock = exitScript.params.timelock;
|
|
@@ -8963,7 +10319,10 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8963
10319
|
}
|
|
8964
10320
|
const vtxos = await this.getVtxos({ withRecoverable: true });
|
|
8965
10321
|
const filteredVtxos = [];
|
|
8966
|
-
for (const vtxo of vtxos) {
|
|
10322
|
+
for (const vtxo of byValueDescending(vtxos)) {
|
|
10323
|
+
if (filteredVtxos.length >= MAX_VTXOS_PER_SETTLEMENT) {
|
|
10324
|
+
break;
|
|
10325
|
+
}
|
|
8967
10326
|
const inputFee = estimator.evalOffchainInput({
|
|
8968
10327
|
amount: BigInt(vtxo.value),
|
|
8969
10328
|
type: vtxo.virtualStatus.state === "swept" ? "recoverable" : "vtxo",
|
|
@@ -8974,20 +10333,31 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
8974
10333
|
if (inputFee.satoshis >= vtxo.value) {
|
|
8975
10334
|
continue;
|
|
8976
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
|
+
}
|
|
8977
10347
|
filteredVtxos.push(vtxo);
|
|
8978
|
-
amount +=
|
|
10348
|
+
amount += net;
|
|
8979
10349
|
}
|
|
8980
10350
|
const inputs = [...filteredBoardingUtxos, ...filteredVtxos];
|
|
8981
10351
|
if (inputs.length === 0) {
|
|
8982
10352
|
throw new Error("No inputs found");
|
|
8983
10353
|
}
|
|
8984
10354
|
const output = {
|
|
8985
|
-
address:
|
|
10355
|
+
address: offchainAddress,
|
|
8986
10356
|
amount: BigInt(amount)
|
|
8987
10357
|
};
|
|
8988
10358
|
const outputFee = estimator.evalOffchainOutput({
|
|
8989
10359
|
amount: output.amount,
|
|
8990
|
-
script:
|
|
10360
|
+
script: offchainOutputScript
|
|
8991
10361
|
});
|
|
8992
10362
|
output.amount -= BigInt(outputFee.satoshis);
|
|
8993
10363
|
if (output.amount <= this.dustAmount) {
|
|
@@ -9004,7 +10374,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9004
10374
|
for (const [index, output] of params.outputs.entries()) {
|
|
9005
10375
|
let script;
|
|
9006
10376
|
try {
|
|
9007
|
-
const addr =
|
|
10377
|
+
const addr = chunkCMPJR3HS_cjs.ArkAddress.decode(output.address);
|
|
9008
10378
|
script = addr.pkScript;
|
|
9009
10379
|
hasOffchainOutputs = true;
|
|
9010
10380
|
} catch {
|
|
@@ -9027,8 +10397,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9027
10397
|
}
|
|
9028
10398
|
}
|
|
9029
10399
|
let outputAssets;
|
|
9030
|
-
const
|
|
9031
|
-
const assetOutputIndex = findDestinationOutputIndex(outputs, destinationScript);
|
|
10400
|
+
const assetOutputIndex = findDestinationOutputIndex(outputs, offchainPkScript);
|
|
9032
10401
|
if (assetInputs.size > 0) {
|
|
9033
10402
|
if (assetOutputIndex === -1) {
|
|
9034
10403
|
throw new Error("Cannot assign assets: no output matches the destination address");
|
|
@@ -9096,6 +10465,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9096
10465
|
eventCallback: eventCallback ? (event) => Promise.resolve(eventCallback(event)) : void 0
|
|
9097
10466
|
});
|
|
9098
10467
|
await this.updateDbAfterSettle(params.inputs, commitmentTxid);
|
|
10468
|
+
await this.maybeRotateBoardingAfterBoard(params.inputs);
|
|
9099
10469
|
return commitmentTxid;
|
|
9100
10470
|
} catch (error) {
|
|
9101
10471
|
const inputIds = params.inputs.map((i) => `${i.txid}:${i.vout}`).join(",");
|
|
@@ -9113,6 +10483,41 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9113
10483
|
});
|
|
9114
10484
|
}
|
|
9115
10485
|
}
|
|
10486
|
+
/**
|
|
10487
|
+
* Rotate the boarding address after a board (rotate-on-board trigger).
|
|
10488
|
+
*
|
|
10489
|
+
* Mirrors {@link WalletReceiveRotator}'s L2 rotation, but driven by a
|
|
10490
|
+
* board instead of a `vtxo_received` event: when a settle consumes at
|
|
10491
|
+
* least one boarding (on-chain) UTXO, the current boarding address has
|
|
10492
|
+
* served its purpose, so we allocate a fresh one via
|
|
10493
|
+
* {@link getNewBoardingAddress}. A settle that consumed only VTXOs (a
|
|
10494
|
+
* renewal / offboard) is not a board and leaves the boarding address
|
|
10495
|
+
* untouched.
|
|
10496
|
+
*
|
|
10497
|
+
* Boarding inputs are the non-VTXO coins (no `virtualStatus`), the same
|
|
10498
|
+
* discriminator {@link handleSettlementFinalizationEvent} uses; the
|
|
10499
|
+
* `typeof` guard skips arknote string inputs before the `in` test.
|
|
10500
|
+
*
|
|
10501
|
+
* No-ops for static / `auto` wallets (no descriptor provider — boarding
|
|
10502
|
+
* stays on its fixed index-0 address). Best-effort and non-fatal: the
|
|
10503
|
+
* settle has already committed and its txid must be returned, so a
|
|
10504
|
+
* rotation failure is logged and swallowed rather than thrown. Funds at
|
|
10505
|
+
* the retired boarding address remain discoverable — the old `boarding`
|
|
10506
|
+
* contract stays active and {@link getBoardingUtxos} fans out over the
|
|
10507
|
+
* full historical boarding set.
|
|
10508
|
+
*/
|
|
10509
|
+
async maybeRotateBoardingAfterBoard(inputs) {
|
|
10510
|
+
if (!this._descriptorProvider) return;
|
|
10511
|
+
const consumedBoarding = inputs.some(
|
|
10512
|
+
(input) => typeof input !== "string" && !("virtualStatus" in input)
|
|
10513
|
+
);
|
|
10514
|
+
if (!consumedBoarding) return;
|
|
10515
|
+
try {
|
|
10516
|
+
await this.getNewBoardingAddress();
|
|
10517
|
+
} catch (e) {
|
|
10518
|
+
console.warn("Failed to rotate boarding address after board", e);
|
|
10519
|
+
}
|
|
10520
|
+
}
|
|
9116
10521
|
async handleSettlementFinalizationEvent(event, inputs, forfeitOutputScript, connectorsGraph) {
|
|
9117
10522
|
const signedForfeits = [];
|
|
9118
10523
|
const isVtxo = (input) => "virtualStatus" in input;
|
|
@@ -9177,7 +10582,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9177
10582
|
index: input.vout,
|
|
9178
10583
|
witnessUtxo: {
|
|
9179
10584
|
amount: BigInt(input.value),
|
|
9180
|
-
script:
|
|
10585
|
+
script: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
|
|
9181
10586
|
},
|
|
9182
10587
|
sighashType: btcSigner.SigHash.DEFAULT,
|
|
9183
10588
|
tapLeafScript: [input.forfeitTapLeafScript]
|
|
@@ -9196,7 +10601,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9196
10601
|
forfeitTx = await this._signerRouter.sign(forfeitTx, [
|
|
9197
10602
|
{
|
|
9198
10603
|
index: 0,
|
|
9199
|
-
lookupScript:
|
|
10604
|
+
lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
|
|
9200
10605
|
}
|
|
9201
10606
|
]);
|
|
9202
10607
|
signedForfeits.push(base.base64.encode(forfeitTx.toPSBT()));
|
|
@@ -9236,7 +10641,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9236
10641
|
if (skip) {
|
|
9237
10642
|
return { skip };
|
|
9238
10643
|
}
|
|
9239
|
-
const sweepTapscript =
|
|
10644
|
+
const sweepTapscript = chunkCMPJR3HS_cjs.CSVMultisigTapscript.encode({
|
|
9240
10645
|
timelock: {
|
|
9241
10646
|
value: event.batchExpiry,
|
|
9242
10647
|
type: event.batchExpiry >= 512n ? "seconds" : "blocks"
|
|
@@ -9323,11 +10728,24 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9323
10728
|
}
|
|
9324
10729
|
return jobs;
|
|
9325
10730
|
}
|
|
10731
|
+
/**
|
|
10732
|
+
* @internal Sign an on-chain boarding exit / sweep transaction, routing
|
|
10733
|
+
* each input to the correct key by its `witnessUtxo.script`: the identity
|
|
10734
|
+
* for index-0 / static boarding, the per-index descriptor for a rotated
|
|
10735
|
+
* boarding UTXO (plan §6-III.3). Used by
|
|
10736
|
+
* {@link VtxoManager.sweepExpiredBoardingUtxos}; without it, the
|
|
10737
|
+
* unilateral exit of a rotated boarding UTXO would be signed with the
|
|
10738
|
+
* wrong (index-0) key and rejected.
|
|
10739
|
+
*/
|
|
10740
|
+
async signOnchainBoardingTx(tx) {
|
|
10741
|
+
const signed = await this._signerRouter.sign(tx, this.inputSigningJobsFromWitnessUtxos(tx));
|
|
10742
|
+
return signed;
|
|
10743
|
+
}
|
|
9326
10744
|
async safeRegisterIntent(intent, inputs) {
|
|
9327
10745
|
try {
|
|
9328
10746
|
return await this.arkProvider.registerIntent(intent);
|
|
9329
10747
|
} catch (error) {
|
|
9330
|
-
if (error instanceof
|
|
10748
|
+
if (error instanceof chunkX2EQLK4O_cjs.ArkError && error.code === 0 && error.message.includes("duplicated input")) {
|
|
9331
10749
|
const deleteIntent = await this.makeDeleteIntentSignature(inputs);
|
|
9332
10750
|
await this.arkProvider.deleteIntent(deleteIntent);
|
|
9333
10751
|
return this.arkProvider.registerIntent(intent);
|
|
@@ -9343,7 +10761,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9343
10761
|
expire_at: 0,
|
|
9344
10762
|
cosigners_public_keys: cosignerPubKeys
|
|
9345
10763
|
};
|
|
9346
|
-
const proof =
|
|
10764
|
+
const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, outputs);
|
|
9347
10765
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9348
10766
|
return {
|
|
9349
10767
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9355,7 +10773,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9355
10773
|
type: "delete",
|
|
9356
10774
|
expire_at: 0
|
|
9357
10775
|
};
|
|
9358
|
-
const proof =
|
|
10776
|
+
const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, []);
|
|
9359
10777
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9360
10778
|
return {
|
|
9361
10779
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9367,7 +10785,7 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9367
10785
|
type: "get-pending-tx",
|
|
9368
10786
|
expire_at: 0
|
|
9369
10787
|
};
|
|
9370
|
-
const proof =
|
|
10788
|
+
const proof = chunkX2EQLK4O_cjs.Intent.create(message, coins, []);
|
|
9371
10789
|
const signedProof = await this._signerRouter.sign(proof, intentProofJobs(coins));
|
|
9372
10790
|
return {
|
|
9373
10791
|
proof: base.base64.encode(signedProof.toPSBT()),
|
|
@@ -9518,12 +10936,16 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9518
10936
|
throw new Error("At least one receiver is required");
|
|
9519
10937
|
}
|
|
9520
10938
|
const offchainTapscript = this.offchainTapscript;
|
|
9521
|
-
const
|
|
10939
|
+
const serverPubKey = this.arkServerPublicKey;
|
|
10940
|
+
const serverUnrollScript = this.serverUnrollScript;
|
|
10941
|
+
const outputAddress = offchainTapscript.address(this.network.hrp, serverPubKey);
|
|
9522
10942
|
const address = outputAddress.encode();
|
|
9523
10943
|
const recipients = validateRecipients(args, Number(this.dustAmount));
|
|
9524
|
-
const
|
|
10944
|
+
const allVirtualCoins = await this.getVtxos({
|
|
9525
10945
|
withRecoverable: false
|
|
9526
10946
|
});
|
|
10947
|
+
const pendingRecovery = await this.pendingRecoveryOutpoints();
|
|
10948
|
+
const virtualCoins = pendingRecovery.size ? allVirtualCoins.filter((c) => !pendingRecovery.has(`${c.txid}:${c.vout}`)) : allVirtualCoins;
|
|
9527
10949
|
const assetChanges = /* @__PURE__ */ new Map();
|
|
9528
10950
|
let selectedCoins = [];
|
|
9529
10951
|
let btcAmountToSelect = 0;
|
|
@@ -9645,33 +11067,128 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9645
11067
|
outputs.push(Extension.create([assetPacket]).txOut());
|
|
9646
11068
|
}
|
|
9647
11069
|
const sentAmount = recipients.reduce((sum, r) => sum + r.amount, 0);
|
|
9648
|
-
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);
|
|
9649
11090
|
try {
|
|
9650
11091
|
const { arkTxid, signedCheckpointTxs } = await this.buildAndSubmitOffchainTx(
|
|
9651
|
-
|
|
9652
|
-
outputs
|
|
11092
|
+
inputs,
|
|
11093
|
+
outputs,
|
|
11094
|
+
persist.serverUnrollScript
|
|
9653
11095
|
);
|
|
9654
11096
|
await this.updateDbAfterOffchainTx(
|
|
9655
|
-
|
|
11097
|
+
inputs,
|
|
9656
11098
|
arkTxid,
|
|
9657
11099
|
signedCheckpointTxs,
|
|
9658
|
-
sentAmount,
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
offchainTapscript,
|
|
9662
|
-
|
|
11100
|
+
persist.sentAmount,
|
|
11101
|
+
persist.changeAmount,
|
|
11102
|
+
persist.changeVout,
|
|
11103
|
+
persist.offchainTapscript,
|
|
11104
|
+
persist.serverPubKey,
|
|
11105
|
+
persist.changeAssets,
|
|
11106
|
+
persist.recordSentHistory ?? true
|
|
9663
11107
|
);
|
|
9664
11108
|
return arkTxid;
|
|
9665
11109
|
} finally {
|
|
9666
|
-
this._removePendingSpends(
|
|
9667
|
-
}
|
|
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
|
+
});
|
|
9668
11185
|
}
|
|
9669
11186
|
/**
|
|
9670
11187
|
* Build an offchain transaction from the given inputs and outputs,
|
|
9671
11188
|
* sign it, submit to the Arkade provider, and finalize.
|
|
9672
11189
|
* @returns The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)
|
|
9673
11190
|
*/
|
|
9674
|
-
async buildAndSubmitOffchainTx(inputs, outputs) {
|
|
11191
|
+
async buildAndSubmitOffchainTx(inputs, outputs, serverUnrollScript = this.serverUnrollScript) {
|
|
9675
11192
|
const offchainTx = buildOffchainTx(
|
|
9676
11193
|
inputs.map((input) => {
|
|
9677
11194
|
return {
|
|
@@ -9680,11 +11197,11 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9680
11197
|
};
|
|
9681
11198
|
}),
|
|
9682
11199
|
outputs,
|
|
9683
|
-
|
|
11200
|
+
serverUnrollScript
|
|
9684
11201
|
);
|
|
9685
11202
|
const arkTxJobs = inputs.map((input, index) => ({
|
|
9686
11203
|
index,
|
|
9687
|
-
lookupScript:
|
|
11204
|
+
lookupScript: chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).pkScript
|
|
9688
11205
|
}));
|
|
9689
11206
|
const checkpointJobs = offchainTx.checkpoints.map(
|
|
9690
11207
|
(c) => this.inputSigningJobsFromWitnessUtxos(c)
|
|
@@ -9754,14 +11271,14 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9754
11271
|
return { arkTxid, signedCheckpointTxs };
|
|
9755
11272
|
}
|
|
9756
11273
|
// mark virtual outputs as spent, save change outputs if any.
|
|
9757
|
-
// `offchainTapscript`
|
|
9758
|
-
// `_txLock` before any `await`; deriving both the
|
|
9759
|
-
// metadata and `primaryAddress` from
|
|
9760
|
-
// record matches the pkScript the server saw on the inbound
|
|
9761
|
-
// transaction, even if `
|
|
9762
|
-
// `this.
|
|
9763
|
-
async updateDbAfterOffchainTx(inputs, arkTxid, signedCheckpointTxs, sentAmount, changeAmount, changeVout, offchainTapscript, changeAssets) {
|
|
9764
|
-
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();
|
|
9765
11282
|
try {
|
|
9766
11283
|
const spentVtxos = [];
|
|
9767
11284
|
const commitmentTxIds = /* @__PURE__ */ new Set();
|
|
@@ -9868,19 +11385,21 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9868
11385
|
[changeVtxo]
|
|
9869
11386
|
);
|
|
9870
11387
|
}
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
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
|
+
}
|
|
9884
11403
|
} catch (e) {
|
|
9885
11404
|
console.warn("error saving offchain tx to repository", e);
|
|
9886
11405
|
throw e;
|
|
@@ -9889,10 +11408,9 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9889
11408
|
// mark virtual outputs as spent/settled, remove boarding inputs
|
|
9890
11409
|
async updateDbAfterSettle(inputs, commitmentTxid) {
|
|
9891
11410
|
try {
|
|
9892
|
-
const boardingAddress = await this.getBoardingAddress();
|
|
9893
11411
|
const spentVtxos = [];
|
|
9894
11412
|
const inputArkTxIds = /* @__PURE__ */ new Set();
|
|
9895
|
-
const
|
|
11413
|
+
const boardingRemovalsByAddress = /* @__PURE__ */ new Map();
|
|
9896
11414
|
const isVtxo = (input) => "virtualStatus" in input;
|
|
9897
11415
|
const vtxoInputs = inputs.filter(isVtxo);
|
|
9898
11416
|
const cm = await this.getContractManager();
|
|
@@ -9914,7 +11432,20 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9914
11432
|
isSpent: true
|
|
9915
11433
|
});
|
|
9916
11434
|
} else {
|
|
9917
|
-
|
|
11435
|
+
let sourceAddress;
|
|
11436
|
+
try {
|
|
11437
|
+
sourceAddress = chunkCMPJR3HS_cjs.VtxoScript.decode(input.tapTree).onchainAddress(
|
|
11438
|
+
this.network
|
|
11439
|
+
);
|
|
11440
|
+
} catch {
|
|
11441
|
+
sourceAddress = this.boardingTapscript.onchainAddress(this.network);
|
|
11442
|
+
}
|
|
11443
|
+
let set = boardingRemovalsByAddress.get(sourceAddress);
|
|
11444
|
+
if (!set) {
|
|
11445
|
+
set = /* @__PURE__ */ new Set();
|
|
11446
|
+
boardingRemovalsByAddress.set(sourceAddress, set);
|
|
11447
|
+
}
|
|
11448
|
+
set.add(`${input.txid}:${input.vout}`);
|
|
9918
11449
|
}
|
|
9919
11450
|
}
|
|
9920
11451
|
if (spentVtxos.length > 0) {
|
|
@@ -9946,14 +11477,12 @@ var Wallet2 = class _Wallet extends ReadonlyWallet {
|
|
|
9946
11477
|
);
|
|
9947
11478
|
}
|
|
9948
11479
|
}
|
|
9949
|
-
|
|
9950
|
-
const currentUtxos = await this.walletRepository.getUtxos(
|
|
9951
|
-
const filtered = currentUtxos.filter(
|
|
9952
|
-
|
|
9953
|
-
);
|
|
9954
|
-
await this.walletRepository.deleteUtxos(boardingAddress);
|
|
11480
|
+
for (const [address, toRemove] of boardingRemovalsByAddress) {
|
|
11481
|
+
const currentUtxos = await this.walletRepository.getUtxos(address);
|
|
11482
|
+
const filtered = currentUtxos.filter((u) => !toRemove.has(`${u.txid}:${u.vout}`));
|
|
11483
|
+
await this.walletRepository.deleteUtxos(address);
|
|
9955
11484
|
if (filtered.length > 0) {
|
|
9956
|
-
await this.walletRepository.saveUtxos(
|
|
11485
|
+
await this.walletRepository.saveUtxos(address, filtered);
|
|
9957
11486
|
}
|
|
9958
11487
|
}
|
|
9959
11488
|
} catch (e) {
|
|
@@ -10161,7 +11690,7 @@ var MessageBus = class {
|
|
|
10161
11690
|
this.initialized = true;
|
|
10162
11691
|
}
|
|
10163
11692
|
async buildServices(config) {
|
|
10164
|
-
const arkProvider = new
|
|
11693
|
+
const arkProvider = new chunkX2EQLK4O_cjs.RestArkProvider(config.arkServer.url);
|
|
10165
11694
|
const storage = {
|
|
10166
11695
|
walletRepository: this.walletRepository,
|
|
10167
11696
|
contractRepository: this.contractRepository
|
|
@@ -10537,7 +12066,7 @@ var Ramps = class {
|
|
|
10537
12066
|
}
|
|
10538
12067
|
amount = amount ?? totalAmount;
|
|
10539
12068
|
const offchainAddress = await this.wallet.getAddress();
|
|
10540
|
-
const offchainAddr =
|
|
12069
|
+
const offchainAddr = chunkCMPJR3HS_cjs.ArkAddress.decode(offchainAddress);
|
|
10541
12070
|
const offchainScript = base.hex.encode(offchainAddr.pkScript);
|
|
10542
12071
|
const outputFee = estimator.evalOffchainOutput({
|
|
10543
12072
|
amount,
|
|
@@ -10635,7 +12164,7 @@ var Ramps = class {
|
|
|
10635
12164
|
let destinationScript;
|
|
10636
12165
|
for (const networkName of networkNames) {
|
|
10637
12166
|
try {
|
|
10638
|
-
const network =
|
|
12167
|
+
const network = chunkCMPJR3HS_cjs.networks[networkName];
|
|
10639
12168
|
const addr = btcSigner.Address(network).decode(destinationAddress);
|
|
10640
12169
|
destinationScript = btcSigner.OutScript.encode(addr);
|
|
10641
12170
|
break;
|
|
@@ -10728,6 +12257,82 @@ var DelegateNotConfiguredError = class extends Error {
|
|
|
10728
12257
|
};
|
|
10729
12258
|
var DelegatorNotConfiguredError = DelegateNotConfiguredError;
|
|
10730
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
|
+
});
|
|
10731
12336
|
var WalletMessageHandler = class {
|
|
10732
12337
|
messageTag;
|
|
10733
12338
|
wallet;
|
|
@@ -10809,7 +12414,9 @@ var WalletMessageHandler = class {
|
|
|
10809
12414
|
// page-side PING / MESSAGE_BUS_NOT_INITIALIZED path triggered by concurrent
|
|
10810
12415
|
// short requests (GET_STATUS, GET_BALANCE, ...).
|
|
10811
12416
|
isLongRunning(message) {
|
|
10812
|
-
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
|
|
10813
12420
|
// step until it hits gapLimit consecutive unused indices. The bus
|
|
10814
12421
|
// deadline must not race the scan; liveness stays covered by PING.
|
|
10815
12422
|
message.type === "RESTORE_WALLET";
|
|
@@ -11175,6 +12782,36 @@ var WalletMessageHandler = class {
|
|
|
11175
12782
|
payload: { txid }
|
|
11176
12783
|
});
|
|
11177
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
|
+
}
|
|
11178
12815
|
case "RESTORE_WALLET": {
|
|
11179
12816
|
const wallet = this.requireWallet();
|
|
11180
12817
|
try {
|
|
@@ -11204,13 +12841,14 @@ var WalletMessageHandler = class {
|
|
|
11204
12841
|
// Wallet methods
|
|
11205
12842
|
async handleInitWallet({ payload }) {
|
|
11206
12843
|
const { arkServerUrl } = payload;
|
|
11207
|
-
this.indexerProvider = new
|
|
12844
|
+
this.indexerProvider = new chunkX2EQLK4O_cjs.RestIndexerProvider(arkServerUrl);
|
|
11208
12845
|
await this.onWalletInitialized();
|
|
11209
12846
|
}
|
|
11210
12847
|
async handleGetBalance() {
|
|
11211
|
-
const [boardingUtxos, allVtxos] = await Promise.all([
|
|
12848
|
+
const [boardingUtxos, allVtxos, pendingOutpoints] = await Promise.all([
|
|
11212
12849
|
this.getAllBoardingUtxos(),
|
|
11213
|
-
this.getVtxosFromRepo()
|
|
12850
|
+
this.getVtxosFromRepo(),
|
|
12851
|
+
this.readonlyWallet ? this.readonlyWallet.pendingRecoveryOutpoints() : Promise.resolve(/* @__PURE__ */ new Set())
|
|
11214
12852
|
]);
|
|
11215
12853
|
let confirmed = 0;
|
|
11216
12854
|
let unconfirmed = 0;
|
|
@@ -11226,8 +12864,11 @@ var WalletMessageHandler = class {
|
|
|
11226
12864
|
let settled = 0;
|
|
11227
12865
|
let preconfirmed = 0;
|
|
11228
12866
|
let recoverable = 0;
|
|
12867
|
+
let pendingRecovery = 0;
|
|
11229
12868
|
for (const vtxo of spendableVtxos) {
|
|
11230
|
-
if (vtxo.
|
|
12869
|
+
if (pendingOutpoints.has(`${vtxo.txid}:${vtxo.vout}`)) {
|
|
12870
|
+
pendingRecovery += vtxo.value;
|
|
12871
|
+
} else if (vtxo.virtualStatus.state === "settled") {
|
|
11231
12872
|
settled += vtxo.value;
|
|
11232
12873
|
} else if (vtxo.virtualStatus.state === "preconfirmed") {
|
|
11233
12874
|
preconfirmed += vtxo.value;
|
|
@@ -11239,7 +12880,7 @@ var WalletMessageHandler = class {
|
|
|
11239
12880
|
}
|
|
11240
12881
|
}
|
|
11241
12882
|
const totalBoarding = confirmed + unconfirmed;
|
|
11242
|
-
const totalOffchain = settled + preconfirmed + recoverable;
|
|
12883
|
+
const totalOffchain = settled + preconfirmed + recoverable + pendingRecovery;
|
|
11243
12884
|
const assetBalances = /* @__PURE__ */ new Map();
|
|
11244
12885
|
for (const vtxo of spendableVtxos) {
|
|
11245
12886
|
if (vtxo.assets) {
|
|
@@ -11263,6 +12904,7 @@ var WalletMessageHandler = class {
|
|
|
11263
12904
|
preconfirmed,
|
|
11264
12905
|
available: settled + preconfirmed,
|
|
11265
12906
|
recoverable,
|
|
12907
|
+
pendingRecovery,
|
|
11266
12908
|
total: totalBoarding + totalOffchain,
|
|
11267
12909
|
assets
|
|
11268
12910
|
};
|
|
@@ -11353,9 +12995,7 @@ var WalletMessageHandler = class {
|
|
|
11353
12995
|
);
|
|
11354
12996
|
}
|
|
11355
12997
|
if (funds.type === "utxo") {
|
|
11356
|
-
const utxos =
|
|
11357
|
-
const boardingAddress = await this.readonlyWallet.getBoardingAddress();
|
|
11358
|
-
await this.walletRepository?.saveUtxos(boardingAddress, utxos);
|
|
12998
|
+
const utxos = await this.readonlyWallet.getBoardingUtxos();
|
|
11359
12999
|
this.scheduleForNextTick(
|
|
11360
13000
|
() => this.tagged({
|
|
11361
13001
|
type: "UTXO_UPDATE",
|
|
@@ -11384,13 +13024,16 @@ var WalletMessageHandler = class {
|
|
|
11384
13024
|
return;
|
|
11385
13025
|
}
|
|
11386
13026
|
const vtxos = await this.getVtxosFromRepo();
|
|
11387
|
-
const
|
|
11388
|
-
const
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
13027
|
+
const boardingAddresses = await this.readonlyWallet.getBoardingAddresses();
|
|
13028
|
+
const fresh = await this.readonlyWallet.getBoardingUtxos();
|
|
13029
|
+
const freshKeys = new Set(fresh.map((u) => `${u.txid}:${u.vout}`));
|
|
13030
|
+
for (const addr of boardingAddresses) {
|
|
13031
|
+
const cached = await this.walletRepository.getUtxos(addr);
|
|
13032
|
+
const kept = cached.filter((u) => freshKeys.has(`${u.txid}:${u.vout}`));
|
|
13033
|
+
if (kept.length === cached.length) continue;
|
|
13034
|
+
await this.walletRepository.deleteUtxos(addr);
|
|
13035
|
+
if (kept.length > 0) await this.walletRepository.saveUtxos(addr, kept);
|
|
13036
|
+
}
|
|
11394
13037
|
const address = await this.readonlyWallet.getAddress();
|
|
11395
13038
|
const txs = await this.buildTransactionHistoryFromCache(vtxos);
|
|
11396
13039
|
if (txs) await this.walletRepository.saveTransactions(address, txs);
|
|
@@ -11647,6 +13290,7 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
|
|
|
11647
13290
|
GET_EXPIRING_VTXOS: 2e4,
|
|
11648
13291
|
GET_EXPIRED_BOARDING_UTXOS: 2e4,
|
|
11649
13292
|
GET_RECOVERABLE_BALANCE: 2e4,
|
|
13293
|
+
GET_DEPRECATED_SIGNER_STATUS: 2e4,
|
|
11650
13294
|
RELOAD_WALLET: 2e4,
|
|
11651
13295
|
// Transactions — need more headroom.
|
|
11652
13296
|
// SETTLE / RECOVER_VTXOS / RENEW_VTXOS go through the streaming path and
|
|
@@ -11662,6 +13306,9 @@ var DEFAULT_MESSAGE_TIMEOUTS = {
|
|
|
11662
13306
|
RECOVER_VTXOS: 5e4,
|
|
11663
13307
|
RENEW_VTXOS: 5e4,
|
|
11664
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,
|
|
11665
13312
|
// RESTORE_WALLET is a streaming/long-running path (sendMessageWithEvents)
|
|
11666
13313
|
// like SETTLE; the value here is kept for type completeness and is never
|
|
11667
13314
|
// enforced as an inactivity deadline.
|
|
@@ -11687,6 +13334,7 @@ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
|
11687
13334
|
"GET_DELEGATE_INFO",
|
|
11688
13335
|
"GET_RECOVERABLE_BALANCE",
|
|
11689
13336
|
"GET_EXPIRED_BOARDING_UTXOS",
|
|
13337
|
+
"GET_DEPRECATED_SIGNER_STATUS",
|
|
11690
13338
|
"GET_VTXOS",
|
|
11691
13339
|
"GET_CONTRACTS",
|
|
11692
13340
|
"GET_CONTRACTS_WITH_VTXOS",
|
|
@@ -12809,6 +14457,42 @@ var ServiceWorkerWallet = class _ServiceWorkerWallet extends ServiceWorkerReadon
|
|
|
12809
14457
|
throw new Error(`Failed to sweep expired boarding utxos: ${e}`);
|
|
12810
14458
|
}
|
|
12811
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
|
+
},
|
|
12812
14496
|
async dispose() {
|
|
12813
14497
|
return;
|
|
12814
14498
|
}
|
|
@@ -12837,12 +14521,12 @@ var OnchainWallet = class _OnchainWallet {
|
|
|
12837
14521
|
* @returns Configured onchain wallet
|
|
12838
14522
|
* @throws Error if the configured identity cannot produce a valid x-only public key
|
|
12839
14523
|
*/
|
|
12840
|
-
static async create(identity, networkName =
|
|
14524
|
+
static async create(identity, networkName = chunkCMPJR3HS_cjs.DEFAULT_NETWORK_NAME, provider) {
|
|
12841
14525
|
const pubkey = await identity.xOnlyPublicKey();
|
|
12842
14526
|
if (!pubkey) {
|
|
12843
14527
|
throw new Error("Invalid configured public key");
|
|
12844
14528
|
}
|
|
12845
|
-
const network =
|
|
14529
|
+
const network = chunkCMPJR3HS_cjs.getNetwork(networkName);
|
|
12846
14530
|
const onchainProvider = provider || new EsploraProvider(ESPLORA_URL[networkName]);
|
|
12847
14531
|
const onchainP2TR = btcSigner.p2tr(pubkey, void 0, network);
|
|
12848
14532
|
return new _OnchainWallet(identity, network, onchainP2TR, onchainProvider);
|
|
@@ -12948,7 +14632,7 @@ var OnchainWallet = class _OnchainWallet {
|
|
|
12948
14632
|
if (!inputs) {
|
|
12949
14633
|
throw new Error("Fee estimation failed");
|
|
12950
14634
|
}
|
|
12951
|
-
let tx = new
|
|
14635
|
+
let tx = new chunkX2EQLK4O_cjs.Transaction();
|
|
12952
14636
|
for (const input of inputs) {
|
|
12953
14637
|
tx.addInput({
|
|
12954
14638
|
txid: input.txid,
|
|
@@ -12979,7 +14663,7 @@ var OnchainWallet = class _OnchainWallet {
|
|
|
12979
14663
|
*/
|
|
12980
14664
|
async bumpP2A(parent) {
|
|
12981
14665
|
const parentVsize = parent.vsize;
|
|
12982
|
-
let child = new
|
|
14666
|
+
let child = new chunkX2EQLK4O_cjs.Transaction({
|
|
12983
14667
|
version: 3,
|
|
12984
14668
|
allowLegacyWitnessUtxo: true
|
|
12985
14669
|
});
|
|
@@ -13714,13 +15398,19 @@ function isHeaderSubscribeResult(v) {
|
|
|
13714
15398
|
const obj = v;
|
|
13715
15399
|
return typeof obj.height === "number" && typeof obj.hex === "string";
|
|
13716
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
|
+
}
|
|
13717
15409
|
function isMissingHeightError(err) {
|
|
13718
|
-
|
|
13719
|
-
return msg.toLowerCase().includes("missingheight");
|
|
15410
|
+
return errorText(err).toLowerCase().includes("missingheight");
|
|
13720
15411
|
}
|
|
13721
15412
|
function isTxNotInBlockError(err) {
|
|
13722
|
-
const
|
|
13723
|
-
const normalized = msg.toLowerCase();
|
|
15413
|
+
const normalized = errorText(err).toLowerCase();
|
|
13724
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");
|
|
13725
15415
|
}
|
|
13726
15416
|
function childTxidFromHex(txHex) {
|
|
@@ -13736,7 +15426,7 @@ exports.BIP322 = void 0;
|
|
|
13736
15426
|
async function sign2(message, identity, network) {
|
|
13737
15427
|
const xOnlyPubKey = await identity.xOnlyPublicKey();
|
|
13738
15428
|
const payment = btcSigner.p2tr(xOnlyPubKey, void 0, network);
|
|
13739
|
-
const toSpend =
|
|
15429
|
+
const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, payment.script, TAG_BIP322);
|
|
13740
15430
|
const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
|
|
13741
15431
|
const signed = await identity.sign(toSign, [0]);
|
|
13742
15432
|
signed.finalizeIdx(0);
|
|
@@ -13794,7 +15484,7 @@ function verifyP2TR(message, witnessItems, pkScript, pubkey) {
|
|
|
13794
15484
|
if (sighashType !== btcSigner.SigHash.DEFAULT && sighashType !== btcSigner.SigHash.ALL) {
|
|
13795
15485
|
return false;
|
|
13796
15486
|
}
|
|
13797
|
-
const toSpend =
|
|
15487
|
+
const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
13798
15488
|
const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
|
|
13799
15489
|
const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
|
|
13800
15490
|
const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
|
|
@@ -13815,7 +15505,7 @@ function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
|
|
|
13815
15505
|
}
|
|
13816
15506
|
const sighashType = sigWithHash[sigWithHash.length - 1];
|
|
13817
15507
|
const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
|
|
13818
|
-
const toSpend =
|
|
15508
|
+
const toSpend = chunkX2EQLK4O_cjs.craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
13819
15509
|
const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
|
|
13820
15510
|
const scriptCode = btcSigner.OutScript.encode({ type: "pkh", hash: addressHash });
|
|
13821
15511
|
const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
|
|
@@ -13868,7 +15558,7 @@ function encodeCompactSize(n) {
|
|
|
13868
15558
|
return buf;
|
|
13869
15559
|
}
|
|
13870
15560
|
function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
|
|
13871
|
-
const tx = new
|
|
15561
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 0 });
|
|
13872
15562
|
tx.addInput({
|
|
13873
15563
|
txid: toSpend.id,
|
|
13874
15564
|
index: 0,
|
|
@@ -13882,12 +15572,12 @@ function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
|
|
|
13882
15572
|
});
|
|
13883
15573
|
tx.addOutput({
|
|
13884
15574
|
amount: 0n,
|
|
13885
|
-
script:
|
|
15575
|
+
script: chunkX2EQLK4O_cjs.OP_RETURN_EMPTY_PKSCRIPT
|
|
13886
15576
|
});
|
|
13887
15577
|
return tx;
|
|
13888
15578
|
}
|
|
13889
15579
|
function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
13890
|
-
const tx = new
|
|
15580
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 0 });
|
|
13891
15581
|
tx.addInput({
|
|
13892
15582
|
txid: toSpend.id,
|
|
13893
15583
|
index: 0,
|
|
@@ -13899,7 +15589,7 @@ function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
|
13899
15589
|
});
|
|
13900
15590
|
tx.addOutput({
|
|
13901
15591
|
amount: 0n,
|
|
13902
|
-
script:
|
|
15592
|
+
script: chunkX2EQLK4O_cjs.OP_RETURN_EMPTY_PKSCRIPT
|
|
13903
15593
|
});
|
|
13904
15594
|
return tx;
|
|
13905
15595
|
}
|
|
@@ -13960,7 +15650,7 @@ exports.Unroll = void 0;
|
|
|
13960
15650
|
if (virtualTxs.txs.length === 0) {
|
|
13961
15651
|
throw new Error(`Tx ${nextTxToBroadcast.txid} not found`);
|
|
13962
15652
|
}
|
|
13963
|
-
const tx =
|
|
15653
|
+
const tx = chunkX2EQLK4O_cjs.Transaction.fromPSBT(base.base64.decode(virtualTxs.txs[0]));
|
|
13964
15654
|
if (nextTxToBroadcast.type === "INDEXER_CHAINED_TX_TYPE_TREE" /* TREE */) {
|
|
13965
15655
|
const input = tx.getInput(0);
|
|
13966
15656
|
if (!input) {
|
|
@@ -14037,12 +15727,12 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
|
|
|
14037
15727
|
if (!exit) {
|
|
14038
15728
|
throw new Error(`no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
14039
15729
|
}
|
|
14040
|
-
const spendingLeaf =
|
|
15730
|
+
const spendingLeaf = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).findLeaf(base.hex.encode(exit.script));
|
|
14041
15731
|
if (!spendingLeaf) {
|
|
14042
15732
|
throw new Error(`spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}`);
|
|
14043
15733
|
}
|
|
14044
15734
|
totalAmount += BigInt(vtxo.value);
|
|
14045
|
-
const sequence =
|
|
15735
|
+
const sequence = chunkCMPJR3HS_cjs.timelockToSequence(exit.params.timelock);
|
|
14046
15736
|
inputs.push({
|
|
14047
15737
|
txid: vtxo.txid,
|
|
14048
15738
|
index: vtxo.vout,
|
|
@@ -14050,7 +15740,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
|
|
|
14050
15740
|
sequence,
|
|
14051
15741
|
witnessUtxo: {
|
|
14052
15742
|
amount: BigInt(vtxo.value),
|
|
14053
|
-
script:
|
|
15743
|
+
script: chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).pkScript
|
|
14054
15744
|
},
|
|
14055
15745
|
sighashType: btcSigner.SigHash.DEFAULT
|
|
14056
15746
|
});
|
|
@@ -14060,7 +15750,7 @@ async function prepareUnrollTransaction(wallet, vtxoTxIds, outputAddress) {
|
|
|
14060
15750
|
btcSigner.TaprootControlBlock.encode(spendingLeaf[0]).length
|
|
14061
15751
|
);
|
|
14062
15752
|
}
|
|
14063
|
-
const tx = new
|
|
15753
|
+
const tx = new chunkX2EQLK4O_cjs.Transaction({ version: 2 });
|
|
14064
15754
|
for (const input of inputs) {
|
|
14065
15755
|
tx.addInput(input);
|
|
14066
15756
|
}
|
|
@@ -14107,7 +15797,7 @@ function doWait(onchainProvider, txid) {
|
|
|
14107
15797
|
};
|
|
14108
15798
|
}
|
|
14109
15799
|
function availableExitPath(confirmedAt, current, vtxo) {
|
|
14110
|
-
const exits =
|
|
15800
|
+
const exits = chunkCMPJR3HS_cjs.VtxoScript.decode(vtxo.tapTree).exitPaths();
|
|
14111
15801
|
for (const exit of exits) {
|
|
14112
15802
|
if (exit.params.timelock.type === "blocks") {
|
|
14113
15803
|
if (current.height >= confirmedAt.height + Number(exit.params.timelock.value)) {
|
|
@@ -14146,7 +15836,7 @@ function decodeArkContract(encoded) {
|
|
|
14146
15836
|
}
|
|
14147
15837
|
function contractFromArkContract(encoded, options = {}) {
|
|
14148
15838
|
const parsed = decodeArkContract(encoded);
|
|
14149
|
-
const handler =
|
|
15839
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.get(parsed.type);
|
|
14150
15840
|
if (!handler) {
|
|
14151
15841
|
throw new Error(`No handler registered for contract type '${parsed.type}'`);
|
|
14152
15842
|
}
|
|
@@ -14160,9 +15850,9 @@ function contractFromArkContract(encoded, options = {}) {
|
|
|
14160
15850
|
metadata: options.metadata
|
|
14161
15851
|
};
|
|
14162
15852
|
}
|
|
14163
|
-
function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix =
|
|
15853
|
+
function contractFromArkContractWithAddress(encoded, serverPubKey, addressPrefix = chunkCMPJR3HS_cjs.DEFAULT_NETWORK.hrp, options = {}) {
|
|
14164
15854
|
const parsed = decodeArkContract(encoded);
|
|
14165
|
-
const handler =
|
|
15855
|
+
const handler = chunkGUTKJMSF_cjs.contractHandlers.getOrThrow(parsed.type);
|
|
14166
15856
|
const params = parsed.data;
|
|
14167
15857
|
const vtxoScript = handler.createScript(params);
|
|
14168
15858
|
return {
|
|
@@ -14236,6 +15926,8 @@ exports.WalletRepositoryImpl = WalletRepositoryImpl;
|
|
|
14236
15926
|
exports.WsElectrumChainSource = WsElectrumChainSource;
|
|
14237
15927
|
exports.buildForfeitTx = buildForfeitTx;
|
|
14238
15928
|
exports.buildOffchainTx = buildOffchainTx;
|
|
15929
|
+
exports.classifyAgainstSignerSet = classifyAgainstSignerSet;
|
|
15930
|
+
exports.classifyContractSigner = classifyContractSigner;
|
|
14239
15931
|
exports.closeDatabase = closeDatabase;
|
|
14240
15932
|
exports.combineTapscriptSigs = combineTapscriptSigs;
|
|
14241
15933
|
exports.contractFromArkContract = contractFromArkContract;
|
|
@@ -14251,6 +15943,7 @@ exports.getRandomId = getRandomId;
|
|
|
14251
15943
|
exports.hasBoardingTxExpired = hasBoardingTxExpired;
|
|
14252
15944
|
exports.isArkContract = isArkContract;
|
|
14253
15945
|
exports.isBatchSignable = isBatchSignable;
|
|
15946
|
+
exports.isCooperativelyMigratable = isCooperativelyMigratable;
|
|
14254
15947
|
exports.isDiscoverable = isDiscoverable;
|
|
14255
15948
|
exports.isExpired = isExpired;
|
|
14256
15949
|
exports.isRecoverable = isRecoverable;
|
|
@@ -14269,10 +15962,12 @@ exports.serializeAssets = serializeAssets;
|
|
|
14269
15962
|
exports.serializeUtxo = serializeUtxo;
|
|
14270
15963
|
exports.serializeVtxo = serializeVtxo;
|
|
14271
15964
|
exports.setupServiceWorker = setupServiceWorker;
|
|
15965
|
+
exports.signerSetFromInfo = signerSetFromInfo;
|
|
15966
|
+
exports.toXOnlySignerHex = toXOnlySignerHex;
|
|
14272
15967
|
exports.validateConnectorsTxGraph = validateConnectorsTxGraph;
|
|
14273
15968
|
exports.validateVtxoTxGraph = validateVtxoTxGraph;
|
|
14274
15969
|
exports.verifyTapscriptSignatures = verifyTapscriptSignatures;
|
|
14275
15970
|
exports.waitForIncomingFunds = waitForIncomingFunds;
|
|
14276
15971
|
exports.warnAndFilterVtxosForScript = warnAndFilterVtxosForScript;
|
|
14277
|
-
//# sourceMappingURL=chunk-
|
|
14278
|
-
//# sourceMappingURL=chunk-
|
|
15972
|
+
//# sourceMappingURL=chunk-H2LX2KKY.cjs.map
|
|
15973
|
+
//# sourceMappingURL=chunk-H2LX2KKY.cjs.map
|