indelible-mcp 5.7.8 → 5.8.0
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/index.js +335 -53
package/LICENSE
CHANGED
|
@@ -15,7 +15,7 @@ Additional Use Grant: You may make use of the Licensed Work for personal,
|
|
|
15
15
|
commercial blockchain storage, session saving, or
|
|
16
16
|
encrypted vault service without written permission
|
|
17
17
|
from the Licensor.
|
|
18
|
-
Change Date: August
|
|
18
|
+
Change Date: August 18, 2030
|
|
19
19
|
Change License: MIT License
|
|
20
20
|
|
|
21
21
|
Terms
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -744,6 +744,52 @@ var init_utxo_cache = __esm({
|
|
|
744
744
|
}
|
|
745
745
|
});
|
|
746
746
|
|
|
747
|
+
// mcp-server/lib/tx-size.js
|
|
748
|
+
function sdkCompatVarIntSize(i) {
|
|
749
|
+
if (i > 2 ** 32) return 9;
|
|
750
|
+
if (i > 2 ** 16) return 5;
|
|
751
|
+
if (i > 253) return 3;
|
|
752
|
+
return 1;
|
|
753
|
+
}
|
|
754
|
+
function pushDataHeaderSize(n) {
|
|
755
|
+
if (n <= 75) return 1;
|
|
756
|
+
if (n <= 255) return 2;
|
|
757
|
+
if (n <= 65535) return 3;
|
|
758
|
+
return 5;
|
|
759
|
+
}
|
|
760
|
+
function txBytes(inputs, { dataBytes = 0, prefixBytes = SAVE_PREFIX_BYTES, p2pkhOuts = 0 } = {}) {
|
|
761
|
+
const k = Math.max(0, Math.floor(inputs));
|
|
762
|
+
let outCount = 1 + p2pkhOuts;
|
|
763
|
+
let outBytes = 8 + 1 + 25;
|
|
764
|
+
outBytes += p2pkhOuts * (8 + 1 + 25);
|
|
765
|
+
if (dataBytes > 0) {
|
|
766
|
+
outCount += 1;
|
|
767
|
+
const script = 1 + 1 + pushDataHeaderSize(prefixBytes) + prefixBytes + pushDataHeaderSize(dataBytes) + dataBytes;
|
|
768
|
+
outBytes += 8 + sdkCompatVarIntSize(script) + script;
|
|
769
|
+
}
|
|
770
|
+
return 4 + sdkCompatVarIntSize(k) + k * INPUT_BYTES + sdkCompatVarIntSize(outCount) + outBytes + 4;
|
|
771
|
+
}
|
|
772
|
+
function feeForInputs(inputs, shape = {}) {
|
|
773
|
+
return Math.ceil(txBytes(inputs, shape) / 1e3 * FEE_PER_KB);
|
|
774
|
+
}
|
|
775
|
+
function fragmentationBudget(shape = {}) {
|
|
776
|
+
return Math.max(FRAG_BUDGET_FLOOR_SATS, Math.ceil(FRAG_BUDGET_RATIO * feeForInputs(1, shape)));
|
|
777
|
+
}
|
|
778
|
+
var INPUT_BYTES, FEE_PER_KB, MIN_SPENDABLE_SATS, MIN_RECORDED_CHANGE, SAVE_PREFIX_BYTES, FRAG_BUDGET_FLOOR_SATS, FRAG_BUDGET_RATIO, MAX_TX_BYTES, MAX_INPUTS_BY_SIGN_BUDGET;
|
|
779
|
+
var init_tx_size = __esm({
|
|
780
|
+
"mcp-server/lib/tx-size.js"() {
|
|
781
|
+
INPUT_BYTES = 149;
|
|
782
|
+
FEE_PER_KB = 150;
|
|
783
|
+
MIN_SPENDABLE_SATS = 24;
|
|
784
|
+
MIN_RECORDED_CHANGE = 547;
|
|
785
|
+
SAVE_PREFIX_BYTES = 19;
|
|
786
|
+
FRAG_BUDGET_FLOOR_SATS = 1e3;
|
|
787
|
+
FRAG_BUDGET_RATIO = 1;
|
|
788
|
+
MAX_TX_BYTES = 12 * 1024 * 1024 - 512;
|
|
789
|
+
MAX_INPUTS_BY_SIGN_BUDGET = 3e3;
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
747
793
|
// mcp-server/lib/spent-by.js
|
|
748
794
|
var spent_by_exports = {};
|
|
749
795
|
__export(spent_by_exports, {
|
|
@@ -807,6 +853,7 @@ var utxo_reservation_exports = {};
|
|
|
807
853
|
__export(utxo_reservation_exports, {
|
|
808
854
|
CONFLICT_RE: () => CONFLICT_RE,
|
|
809
855
|
TX_KNOWN_RE: () => TX_KNOWN_RE,
|
|
856
|
+
claimRefusalMessage: () => claimRefusalMessage,
|
|
810
857
|
claimSpendableUtxos: () => claimSpendableUtxos,
|
|
811
858
|
linkOrRefuse: () => linkOrRefuse,
|
|
812
859
|
linkReservation: () => linkReservation,
|
|
@@ -814,6 +861,7 @@ __export(utxo_reservation_exports, {
|
|
|
814
861
|
probeTxVerdict: () => probeTxVerdict,
|
|
815
862
|
reconcileHooks: () => reconcileHooks,
|
|
816
863
|
reconcileReservations: () => reconcileReservations,
|
|
864
|
+
selectInputs: () => selectInputs,
|
|
817
865
|
settleFromWriteResult: () => settleFromWriteResult,
|
|
818
866
|
settleReservation: () => settleReservation
|
|
819
867
|
});
|
|
@@ -911,6 +959,82 @@ function reservedInputSet(store, { exceptToken } = {}) {
|
|
|
911
959
|
}
|
|
912
960
|
return s;
|
|
913
961
|
}
|
|
962
|
+
function claimRefusalMessage(claim2) {
|
|
963
|
+
const r = claim2 && claim2.reason;
|
|
964
|
+
if (r === "UNECONOMIC_FRAGMENTATION") {
|
|
965
|
+
return `This save is blocked to protect your money: your wallet holds enough (${claim2.available} sats), but it is spread across so many small coins that the transaction fee would be ${claim2.feeTotal} sats - ${claim2.fragSats} sats MORE than the ${claim2.baseline} sats this save would cost from a single coin (limit: ${claim2.budgetSats} sats extra). Do NOT add funds - you already have them, and adding more will not help. This needs a consolidation step the software does not offer yet; contact support at indeliblebsv@gmail.com and mention UNECONOMIC_FRAGMENTATION. Your data is not lost - nothing was broadcast and nothing was spent.`;
|
|
966
|
+
}
|
|
967
|
+
if (r === "TOO_MANY_INPUTS") {
|
|
968
|
+
return `This save cannot be funded in one transaction: it would need ${claim2.inputsNeeded} coins, above the ${claim2.cap}-input signing limit. Nothing was spent. Contact support at indeliblebsv@gmail.com and mention TOO_MANY_INPUTS.`;
|
|
969
|
+
}
|
|
970
|
+
if (r === "TX_TOO_LARGE") {
|
|
971
|
+
return `This save would produce a ${claim2.txBytes.toLocaleString()}-byte transaction, above the ${claim2.maxTxBytes.toLocaleString()}-byte network envelope - too large to broadcast. Nothing was spent. Split the content into smaller saves.`;
|
|
972
|
+
}
|
|
973
|
+
if (r === "NO_ECONOMIC_COINS") {
|
|
974
|
+
return `Your wallet holds ${claim2.dust && claim2.dust.sats ? claim2.dust.sats : 0} sats, but only in coins so small that each costs more in fees (~23 sats) than it adds. These cannot fund a save. Add funds in ONE normal-sized payment - the small coins remain yours, but stay unusable until a consolidation feature exists.`;
|
|
975
|
+
}
|
|
976
|
+
if (r === "INSUFFICIENT_FUNDS") {
|
|
977
|
+
const held = claim2.reservedSats > 0 ? ` (${claim2.reservedSats} more sats are temporarily reserved by another agent on this box and free up shortly)` : "";
|
|
978
|
+
return `Not enough funds: this save needs about ${claim2.need} sats and the wallet holds ${claim2.available} spendable${held}. Fund your wallet - a single payment of at least ${claim2.need} sats always suffices.`;
|
|
979
|
+
}
|
|
980
|
+
if (r === "NO_COINS") return "No UTXOs available. Fund your wallet.";
|
|
981
|
+
return "No UTXOs available. Fund your wallet.";
|
|
982
|
+
}
|
|
983
|
+
function selectInputs(free, spend = {}) {
|
|
984
|
+
const {
|
|
985
|
+
dataBytes = 0,
|
|
986
|
+
prefixBytes,
|
|
987
|
+
p2pkhOuts = 0,
|
|
988
|
+
extraOutSats = 0,
|
|
989
|
+
allowFragmentationCost = false,
|
|
990
|
+
maxInputs = MAX_INPUTS_BY_SIGN_BUDGET,
|
|
991
|
+
maxTxBytes = MAX_TX_BYTES
|
|
992
|
+
} = spend;
|
|
993
|
+
const shape = { dataBytes, p2pkhOuts, ...prefixBytes !== void 0 ? { prefixBytes } : {} };
|
|
994
|
+
const raw = (free || []).filter((u) => Number.isFinite(u?.value) && u.value > 0);
|
|
995
|
+
const coins = raw.filter((u) => u.value >= MIN_SPENDABLE_SATS);
|
|
996
|
+
const rawSats = raw.reduce((n, u) => n + u.value, 0);
|
|
997
|
+
const available = coins.reduce((n, u) => n + u.value, 0);
|
|
998
|
+
const dust = { count: raw.length - coins.length, sats: rawSats - available };
|
|
999
|
+
const target = (k) => feeForInputs(k, shape) + extraOutSats + MIN_RECORDED_CHANGE;
|
|
1000
|
+
if (raw.length === 0) return { ok: false, reason: "NO_COINS", need: target(1), available: 0, dust };
|
|
1001
|
+
if (coins.length === 0) return { ok: false, reason: "NO_ECONOMIC_COINS", need: target(1), available: 0, dust };
|
|
1002
|
+
if (txBytes(1, shape) > maxTxBytes) {
|
|
1003
|
+
return { ok: false, reason: "TX_TOO_LARGE", txBytes: txBytes(1, shape), maxTxBytes, available, dust };
|
|
1004
|
+
}
|
|
1005
|
+
const ascending = [...coins].sort((a, b) => a.value - b.value);
|
|
1006
|
+
const single = ascending.find((u) => u.value >= target(1));
|
|
1007
|
+
if (single) return { ok: true, inputs: [single], fee: feeForInputs(1, shape), fragSats: 0, dust };
|
|
1008
|
+
const descending = [...coins].sort((a, b) => b.value - a.value);
|
|
1009
|
+
const picked = [];
|
|
1010
|
+
let total = 0;
|
|
1011
|
+
let kMin = 0;
|
|
1012
|
+
for (const u of descending) {
|
|
1013
|
+
picked.push(u);
|
|
1014
|
+
total += u.value;
|
|
1015
|
+
if (total >= target(picked.length)) {
|
|
1016
|
+
kMin = picked.length;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (!kMin) {
|
|
1021
|
+
return { ok: false, reason: "INSUFFICIENT_FUNDS", need: target(1), available, dust };
|
|
1022
|
+
}
|
|
1023
|
+
if (txBytes(kMin, shape) > maxTxBytes) {
|
|
1024
|
+
return { ok: false, reason: "TX_TOO_LARGE", txBytes: txBytes(kMin, shape), maxTxBytes, available, dust };
|
|
1025
|
+
}
|
|
1026
|
+
if (kMin > maxInputs) {
|
|
1027
|
+
return { ok: false, reason: "TOO_MANY_INPUTS", inputsNeeded: kMin, cap: maxInputs, available, dust };
|
|
1028
|
+
}
|
|
1029
|
+
const baseline = feeForInputs(1, shape);
|
|
1030
|
+
const feeTotal = feeForInputs(kMin, shape);
|
|
1031
|
+
const fragSats = feeTotal - baseline;
|
|
1032
|
+
const budgetSats = fragmentationBudget(shape);
|
|
1033
|
+
if (fragSats > budgetSats && !allowFragmentationCost) {
|
|
1034
|
+
return { ok: false, reason: "UNECONOMIC_FRAGMENTATION", fragSats, budgetSats, feeTotal, baseline, inputsNeeded: kMin, available, dust };
|
|
1035
|
+
}
|
|
1036
|
+
return { ok: true, inputs: picked, fee: feeTotal, fragSats, dust };
|
|
1037
|
+
}
|
|
914
1038
|
async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
|
|
915
1039
|
const { waitMs = DEFAULT_WAIT_MS, pollMs = DEFAULT_POLL_MS, leaseMs = DEFAULT_LEASE_MS, now = Date.now } = opts;
|
|
916
1040
|
const deadline = now() + waitMs;
|
|
@@ -933,12 +1057,20 @@ async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
|
|
|
933
1057
|
if (free.length === 0) {
|
|
934
1058
|
return { busy: (candidates || []).length > 0 };
|
|
935
1059
|
}
|
|
1060
|
+
const selected = selectInputs(free, opts.spend);
|
|
1061
|
+
if (!selected.ok) {
|
|
1062
|
+
const wholeWallet = selectInputs(candidates || [], opts.spend);
|
|
1063
|
+
if (wholeWallet.ok) return { busy: true };
|
|
1064
|
+
const { ok: _ok, ...evidence } = selected;
|
|
1065
|
+
const reservedSats = (candidates || []).filter((u) => reserved.has(inputKey(u))).reduce((s, u) => s + (u?.value || 0), 0);
|
|
1066
|
+
return { refused: true, ...evidence, reservedSats };
|
|
1067
|
+
}
|
|
936
1068
|
const token = randomUUID();
|
|
937
1069
|
store.reservations[token] = {
|
|
938
1070
|
owner_token: token,
|
|
939
1071
|
pid: process.pid,
|
|
940
1072
|
address,
|
|
941
|
-
inputs:
|
|
1073
|
+
inputs: selected.inputs,
|
|
942
1074
|
claimed_at: now(),
|
|
943
1075
|
lease_until: now() + leaseMs,
|
|
944
1076
|
build_cache_key: null,
|
|
@@ -946,9 +1078,10 @@ async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
|
|
|
946
1078
|
unknown_cycles: 0
|
|
947
1079
|
};
|
|
948
1080
|
writeReservations(store);
|
|
949
|
-
return { token, utxos:
|
|
1081
|
+
return { token, utxos: selected.inputs };
|
|
950
1082
|
}, LOCK_OPTS);
|
|
951
1083
|
if (claimed.token) return claimed;
|
|
1084
|
+
if (claimed.refused) return { token: null, utxos: [], ...claimed };
|
|
952
1085
|
if (!claimed.busy) return { token: null, utxos: [] };
|
|
953
1086
|
if (!_saidWaiting) {
|
|
954
1087
|
_saidWaiting = true;
|
|
@@ -1200,6 +1333,7 @@ var init_utxo_reservation = __esm({
|
|
|
1200
1333
|
"mcp-server/lib/utxo-reservation.js"() {
|
|
1201
1334
|
init_file_lock();
|
|
1202
1335
|
init_utxo_cache();
|
|
1336
|
+
init_tx_size();
|
|
1203
1337
|
CACHE_PATH2 = process.env.INDELIBLE_UTXO_CACHE || join6(homedir5(), ".indelible", "utxo-cache.json");
|
|
1204
1338
|
RES_PATH = `${CACHE_PATH2}.reservations.json`;
|
|
1205
1339
|
LOCK_PATH = `${CACHE_PATH2}.g401.lock`;
|
|
@@ -1233,6 +1367,7 @@ __export(spv_exports, {
|
|
|
1233
1367
|
checkConfirmation: () => checkConfirmation,
|
|
1234
1368
|
checkHealth: () => checkHealth,
|
|
1235
1369
|
extractEncryptedFromTx: () => extractEncryptedFromTx,
|
|
1370
|
+
fetchSourceTransactions: () => fetchSourceTransactions,
|
|
1236
1371
|
getAddressHistory: () => getAddressHistory,
|
|
1237
1372
|
getBridges: () => getBridges,
|
|
1238
1373
|
getRawTx: () => getRawTx,
|
|
@@ -1435,6 +1570,26 @@ async function getRawTx(txid) {
|
|
|
1435
1570
|
`);
|
|
1436
1571
|
return text;
|
|
1437
1572
|
}
|
|
1573
|
+
async function fetchSourceTransactions(utxos, fetchRaw = getRawTx) {
|
|
1574
|
+
const wanted = [...new Set((utxos || []).map((u) => String(u.tx_hash).trim()))];
|
|
1575
|
+
const byTxid = /* @__PURE__ */ new Map();
|
|
1576
|
+
const t0 = Date.now();
|
|
1577
|
+
let cursor = 0;
|
|
1578
|
+
const worker = async () => {
|
|
1579
|
+
for (; ; ) {
|
|
1580
|
+
const i = cursor++;
|
|
1581
|
+
if (i >= wanted.length) return;
|
|
1582
|
+
const txid = wanted[i];
|
|
1583
|
+
byTxid.set(txid, Transaction.fromHex(await fetchRaw(txid)));
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
await Promise.all(Array.from({ length: Math.min(SOURCE_FETCH_CONCURRENCY, wanted.length) }, worker));
|
|
1587
|
+
if ((utxos || []).length > 1) {
|
|
1588
|
+
process.stderr.write(`[MCP] sources: ${wanted.length} distinct for ${utxos.length} inputs (${Date.now() - t0}ms)
|
|
1589
|
+
`);
|
|
1590
|
+
}
|
|
1591
|
+
return byTxid;
|
|
1592
|
+
}
|
|
1438
1593
|
function bcastTimeoutMs(rawTxHex) {
|
|
1439
1594
|
const txMB = (rawTxHex?.length || 0) / 2 / 1048576;
|
|
1440
1595
|
return Math.min(18e4, Math.round(15e3 + txMB * 12e3));
|
|
@@ -1702,9 +1857,10 @@ async function buildOpReturnTx(wif, utxos, dataStr) {
|
|
|
1702
1857
|
const tx = new Transaction();
|
|
1703
1858
|
const p2pkh = new P2PKH();
|
|
1704
1859
|
const lockingScript = p2pkh.lock(address);
|
|
1860
|
+
const sourceByTxid = await fetchSourceTransactions(utxos);
|
|
1705
1861
|
for (const utxo of utxos) {
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1862
|
+
const sourceTransaction = sourceByTxid.get(String(utxo.tx_hash).trim());
|
|
1863
|
+
if (!sourceTransaction) throw new Error(`source transaction unavailable for input ${utxo.tx_hash}:${utxo.tx_pos}`);
|
|
1708
1864
|
tx.addInput({
|
|
1709
1865
|
sourceTransaction,
|
|
1710
1866
|
sourceOutputIndex: utxo.tx_pos,
|
|
@@ -1734,6 +1890,29 @@ async function buildOpReturnTx(wif, utxos, dataStr) {
|
|
|
1734
1890
|
change: true
|
|
1735
1891
|
});
|
|
1736
1892
|
await tx.fee(new SatoshisPerKilobyte(150));
|
|
1893
|
+
{
|
|
1894
|
+
const _ci = tx.outputs.findIndex((o) => o.change);
|
|
1895
|
+
if (_ci >= 0) {
|
|
1896
|
+
const _cv = tx.outputs[_ci].satoshis || 0;
|
|
1897
|
+
if (_cv > 0 && _cv <= 546) tx.outputs.splice(_ci, 1);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
{
|
|
1901
|
+
if (tx.inputs.length === 0) {
|
|
1902
|
+
const e = new Error("ZERO_INPUT_BUILD: refusing to sign a transaction with no inputs \u2014 the caller handed an empty coin set");
|
|
1903
|
+
e.code = "ZERO_INPUT_BUILD";
|
|
1904
|
+
throw e;
|
|
1905
|
+
}
|
|
1906
|
+
const _totalIn = (utxos || []).reduce((s, u) => s + (u?.value || 0), 0);
|
|
1907
|
+
const _outSum = tx.outputs.reduce((s, o) => s + (o.satoshis || 0), 0);
|
|
1908
|
+
const _residual = _totalIn - _outSum;
|
|
1909
|
+
const _model = await new SatoshisPerKilobyte(150).computeFee(tx);
|
|
1910
|
+
if (_residual < _model) {
|
|
1911
|
+
const e = new Error(`UNDERFUNDED_BUILD: inputs ${_totalIn} sats cannot cover outputs ${_outSum} + fee ${_model} \u2014 refusing to sign an under-paying transaction`);
|
|
1912
|
+
e.code = "UNDERFUNDED_BUILD";
|
|
1913
|
+
throw e;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1737
1916
|
await tx.sign();
|
|
1738
1917
|
return {
|
|
1739
1918
|
txHex: tx.toHex(),
|
|
@@ -1747,9 +1926,10 @@ async function buildOpReturnTxWithChange(wif, utxos, dataStr) {
|
|
|
1747
1926
|
const p2pkh = new P2PKH();
|
|
1748
1927
|
const lockingScript = p2pkh.lock(address);
|
|
1749
1928
|
let totalInput = 0;
|
|
1929
|
+
const sourceByTxid = await fetchSourceTransactions(utxos);
|
|
1750
1930
|
for (const utxo of utxos) {
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1931
|
+
const sourceTransaction = sourceByTxid.get(String(utxo.tx_hash).trim());
|
|
1932
|
+
if (!sourceTransaction) throw new Error(`source transaction unavailable for input ${utxo.tx_hash}:${utxo.tx_pos}`);
|
|
1753
1933
|
tx.addInput({
|
|
1754
1934
|
sourceTransaction,
|
|
1755
1935
|
sourceOutputIndex: utxo.tx_pos,
|
|
@@ -1780,6 +1960,29 @@ async function buildOpReturnTxWithChange(wif, utxos, dataStr) {
|
|
|
1780
1960
|
change: true
|
|
1781
1961
|
});
|
|
1782
1962
|
await tx.fee(new SatoshisPerKilobyte(150));
|
|
1963
|
+
{
|
|
1964
|
+
const _ci = tx.outputs.findIndex((o) => o.change);
|
|
1965
|
+
if (_ci >= 0) {
|
|
1966
|
+
const _cv = tx.outputs[_ci].satoshis || 0;
|
|
1967
|
+
if (_cv > 0 && _cv <= 546) tx.outputs.splice(_ci, 1);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
{
|
|
1971
|
+
if (tx.inputs.length === 0) {
|
|
1972
|
+
const e = new Error("ZERO_INPUT_BUILD: refusing to sign a transaction with no inputs \u2014 the caller handed an empty coin set");
|
|
1973
|
+
e.code = "ZERO_INPUT_BUILD";
|
|
1974
|
+
throw e;
|
|
1975
|
+
}
|
|
1976
|
+
const _totalIn = (utxos || []).reduce((s, u) => s + (u?.value || 0), 0);
|
|
1977
|
+
const _outSum = tx.outputs.reduce((s, o) => s + (o.satoshis || 0), 0);
|
|
1978
|
+
const _residual = _totalIn - _outSum;
|
|
1979
|
+
const _model = await new SatoshisPerKilobyte(150).computeFee(tx);
|
|
1980
|
+
if (_residual < _model) {
|
|
1981
|
+
const e = new Error(`UNDERFUNDED_BUILD: inputs ${_totalIn} sats cannot cover outputs ${_outSum} + fee ${_model} \u2014 refusing to sign an under-paying transaction`);
|
|
1982
|
+
e.code = "UNDERFUNDED_BUILD";
|
|
1983
|
+
throw e;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1783
1986
|
await tx.sign();
|
|
1784
1987
|
const txId = tx.id("hex");
|
|
1785
1988
|
const txHex = tx.toHex();
|
|
@@ -1805,9 +2008,10 @@ async function buildPaymentTx(wif, utxos, payToAddress, satoshis) {
|
|
|
1805
2008
|
const p2pkh = new P2PKH();
|
|
1806
2009
|
const lockingScript = p2pkh.lock(address);
|
|
1807
2010
|
let totalInput = 0;
|
|
2011
|
+
const sourceByTxid = await fetchSourceTransactions(utxos);
|
|
1808
2012
|
for (const utxo of utxos) {
|
|
1809
|
-
const
|
|
1810
|
-
|
|
2013
|
+
const sourceTransaction = sourceByTxid.get(String(utxo.tx_hash).trim());
|
|
2014
|
+
if (!sourceTransaction) throw new Error(`source transaction unavailable for input ${utxo.tx_hash}:${utxo.tx_pos}`);
|
|
1811
2015
|
tx.addInput({
|
|
1812
2016
|
sourceTransaction,
|
|
1813
2017
|
sourceOutputIndex: utxo.tx_pos,
|
|
@@ -1830,6 +2034,29 @@ async function buildPaymentTx(wif, utxos, payToAddress, satoshis) {
|
|
|
1830
2034
|
change: true
|
|
1831
2035
|
});
|
|
1832
2036
|
await tx.fee(new SatoshisPerKilobyte(150));
|
|
2037
|
+
{
|
|
2038
|
+
const _ci = tx.outputs.findIndex((o) => o.change);
|
|
2039
|
+
if (_ci >= 0) {
|
|
2040
|
+
const _cv = tx.outputs[_ci].satoshis || 0;
|
|
2041
|
+
if (_cv > 0 && _cv <= 546) tx.outputs.splice(_ci, 1);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
{
|
|
2045
|
+
if (tx.inputs.length === 0) {
|
|
2046
|
+
const e = new Error("ZERO_INPUT_BUILD: refusing to sign a transaction with no inputs \u2014 the caller handed an empty coin set");
|
|
2047
|
+
e.code = "ZERO_INPUT_BUILD";
|
|
2048
|
+
throw e;
|
|
2049
|
+
}
|
|
2050
|
+
const _totalIn = (utxos || []).reduce((s, u) => s + (u?.value || 0), 0);
|
|
2051
|
+
const _outSum = tx.outputs.reduce((s, o) => s + (o.satoshis || 0), 0);
|
|
2052
|
+
const _residual = _totalIn - _outSum;
|
|
2053
|
+
const _model = await new SatoshisPerKilobyte(150).computeFee(tx);
|
|
2054
|
+
if (_residual < _model) {
|
|
2055
|
+
const e = new Error(`UNDERFUNDED_BUILD: inputs ${_totalIn} sats cannot cover outputs ${_outSum} + fee ${_model} \u2014 refusing to sign an under-paying transaction`);
|
|
2056
|
+
e.code = "UNDERFUNDED_BUILD";
|
|
2057
|
+
throw e;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
1833
2060
|
await tx.sign();
|
|
1834
2061
|
const txId = tx.id("hex");
|
|
1835
2062
|
const txHex = tx.toHex();
|
|
@@ -1857,7 +2084,7 @@ function extractEncryptedFromTx(tx) {
|
|
|
1857
2084
|
const match = str.match(/([A-Za-z0-9+/=]{12,}):([A-Za-z0-9+/=]{20,}):([A-Za-z0-9+/=]{20,})/);
|
|
1858
2085
|
return match ? match[0] : null;
|
|
1859
2086
|
}
|
|
1860
|
-
var SEED_BRIDGES, OLD_BRIDGE_IPS, bridgeHealth, HEALTH_CHECK_INTERVAL, MAX_FAILURES, migrationDone, ARC_NETWORK_STATUSES, StoredError, _stickyBridge, STICKY_WINDOW_MS;
|
|
2087
|
+
var SEED_BRIDGES, OLD_BRIDGE_IPS, bridgeHealth, HEALTH_CHECK_INTERVAL, MAX_FAILURES, migrationDone, SOURCE_FETCH_CONCURRENCY, ARC_NETWORK_STATUSES, StoredError, _stickyBridge, STICKY_WINDOW_MS;
|
|
1861
2088
|
var init_spv = __esm({
|
|
1862
2089
|
"mcp-server/lib/spv.js"() {
|
|
1863
2090
|
init_config_customer();
|
|
@@ -1884,6 +2111,7 @@ var init_spv = __esm({
|
|
|
1884
2111
|
HEALTH_CHECK_INTERVAL = 6e4;
|
|
1885
2112
|
MAX_FAILURES = 3;
|
|
1886
2113
|
migrationDone = false;
|
|
2114
|
+
SOURCE_FETCH_CONCURRENCY = 8;
|
|
1887
2115
|
ARC_NETWORK_STATUSES = /* @__PURE__ */ new Set([
|
|
1888
2116
|
// Tightened 2026-06-17 to match the relay-federation bridge: a node must have
|
|
1889
2117
|
// TAKEN the tx — dropped ANNOUNCED_TO_NETWORK/REQUESTED_BY_NETWORK (ARC merely
|
|
@@ -2392,17 +2620,19 @@ async function commitSession(session, wif) {
|
|
|
2392
2620
|
const apiKey = config2?.api_key || null;
|
|
2393
2621
|
await checkTier(apiKey);
|
|
2394
2622
|
const RECONCILE_HOOKS = { checkConfirmation: (id) => checkConfirmation(id), broadcast: (hex) => broadcastTx(hex), readEntry };
|
|
2623
|
+
const payload = {
|
|
2624
|
+
protocol: "indelible.claude-code",
|
|
2625
|
+
encrypted: session.encrypted,
|
|
2626
|
+
wrap_owner: session.wrap_owner || null
|
|
2627
|
+
};
|
|
2628
|
+
const payloadStr = JSON.stringify(payload);
|
|
2629
|
+
const SPEND = { dataBytes: Buffer.byteLength(payloadStr) };
|
|
2395
2630
|
let claim2 = null;
|
|
2396
2631
|
try {
|
|
2397
|
-
claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS });
|
|
2632
|
+
claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS, spend: SPEND });
|
|
2398
2633
|
const utxos = claim2.utxos;
|
|
2399
2634
|
if (utxos && utxos.length > 0) {
|
|
2400
|
-
|
|
2401
|
-
protocol: "indelible.claude-code",
|
|
2402
|
-
encrypted: session.encrypted,
|
|
2403
|
-
wrap_owner: session.wrap_owner || null
|
|
2404
|
-
};
|
|
2405
|
-
let { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload));
|
|
2635
|
+
let { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, payloadStr);
|
|
2406
2636
|
await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
|
|
2407
2637
|
let writeReceipt;
|
|
2408
2638
|
try {
|
|
@@ -2410,10 +2640,10 @@ async function commitSession(session, wif) {
|
|
|
2410
2640
|
} catch (bErr) {
|
|
2411
2641
|
const settled = await settleFromWriteResult(claim2.token, { error: bErr, changeUtxos });
|
|
2412
2642
|
if (settled.reason !== "conflict") throw bErr;
|
|
2413
|
-
claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS });
|
|
2643
|
+
claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS, spend: SPEND });
|
|
2414
2644
|
const fresh = claim2.utxos;
|
|
2415
2645
|
if (!fresh || fresh.length === 0) throw bErr;
|
|
2416
|
-
({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, fresh,
|
|
2646
|
+
({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, fresh, payloadStr));
|
|
2417
2647
|
await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
|
|
2418
2648
|
try {
|
|
2419
2649
|
writeReceipt = await broadcastTx(txHex);
|
|
@@ -2465,7 +2695,7 @@ async function commitSession(session, wif) {
|
|
|
2465
2695
|
}
|
|
2466
2696
|
if (claim2?.token) await settleReservation(claim2.token, "abort").catch(() => {
|
|
2467
2697
|
});
|
|
2468
|
-
throw new Error(
|
|
2698
|
+
throw new Error(claimRefusalMessage(claim2));
|
|
2469
2699
|
}
|
|
2470
2700
|
function buildReceipt(writeResult, { txId, fee, txSize, indexed } = {}) {
|
|
2471
2701
|
const w = writeResult || {};
|
|
@@ -3379,7 +3609,7 @@ async function saveStyle(rulesText, styleName, description) {
|
|
|
3379
3609
|
};
|
|
3380
3610
|
let claim2 = null;
|
|
3381
3611
|
try {
|
|
3382
|
-
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports) });
|
|
3612
|
+
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports), spend: { dataBytes: Buffer.byteLength(JSON.stringify(payload)) } });
|
|
3383
3613
|
} catch (e) {
|
|
3384
3614
|
if (e.code === "WALLET_BUSY_RESERVED") return { success: false, error: e.message, retryable: true };
|
|
3385
3615
|
throw e;
|
|
@@ -3388,7 +3618,7 @@ async function saveStyle(rulesText, styleName, description) {
|
|
|
3388
3618
|
const utxos = claim2.utxos;
|
|
3389
3619
|
if (!utxos || utxos.length === 0) {
|
|
3390
3620
|
await settleReservation(claim2.token, "abort");
|
|
3391
|
-
return { success: false, error:
|
|
3621
|
+
return { success: false, error: claimRefusalMessage(claim2) };
|
|
3392
3622
|
}
|
|
3393
3623
|
const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload));
|
|
3394
3624
|
await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
|
|
@@ -8378,9 +8608,16 @@ async function fulfilNotaryOrder({ scope = "", agentName, order, spv = null } =
|
|
|
8378
8608
|
};
|
|
8379
8609
|
}
|
|
8380
8610
|
const claimRes = await claimSpendableUtxos(agentAddress, async () => paymentUtxos, {
|
|
8381
|
-
reconcile: reconcileHooks({ checkConfirmation: spv?.checkConfirmation || checkConfirmation, broadcast: spv?.broadcastTx || broadcastTx })
|
|
8611
|
+
reconcile: reconcileHooks({ checkConfirmation: spv?.checkConfirmation || checkConfirmation, broadcast: spv?.broadcastTx || broadcastTx }),
|
|
8612
|
+
// AUDIT F5 (wf_dda0064a): the claim used the DEFAULT shape (dataBytes 0), so a payment
|
|
8613
|
+
// could pass the ~576-sat default target yet underfund the real anchor build. Size from
|
|
8614
|
+
// the anchor's actual bytes; the F6 builder invariant backstops whatever remains.
|
|
8615
|
+
spend: { dataBytes: Buffer.byteLength(data) }
|
|
8382
8616
|
});
|
|
8383
8617
|
if (!claimRes || !claimRes.token || !claimRes.utxos?.length) {
|
|
8618
|
+
if (claimRes?.refused) {
|
|
8619
|
+
return { state: "error", reason: "PAYMENT_OUTPOINT_UNFUNDABLE", detail: claimRefusalMessage(claimRes).slice(0, 300) };
|
|
8620
|
+
}
|
|
8384
8621
|
return { state: "error", reason: "PAYMENT_OUTPOINT_RESERVED", detail: "another worker holds the payment coin right now \u2014 retry shortly" };
|
|
8385
8622
|
}
|
|
8386
8623
|
claimTok = claimRes.token;
|
|
@@ -8403,7 +8640,7 @@ async function fulfilNotaryOrder({ scope = "", agentName, order, spv = null } =
|
|
|
8403
8640
|
} catch (e) {
|
|
8404
8641
|
if (claimTok) {
|
|
8405
8642
|
try {
|
|
8406
|
-
await settleReservation(claimTok, "
|
|
8643
|
+
await settleReservation(claimTok, "abort", {});
|
|
8407
8644
|
} catch {
|
|
8408
8645
|
}
|
|
8409
8646
|
}
|
|
@@ -10968,6 +11205,7 @@ init_save_log();
|
|
|
10968
11205
|
init_spv();
|
|
10969
11206
|
init_build_cache();
|
|
10970
11207
|
init_utxo_cache();
|
|
11208
|
+
init_tx_size();
|
|
10971
11209
|
init_utxo_reservation();
|
|
10972
11210
|
import { gzipSync } from "zlib";
|
|
10973
11211
|
import { readFile as readFile4, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -11086,10 +11324,33 @@ async function saveFile(filePath, options = {}) {
|
|
|
11086
11324
|
const encrypted = encrypt("gz:" + compressed.toString("base64"), wif);
|
|
11087
11325
|
const heldClaims = [];
|
|
11088
11326
|
let utxos = options.utxos || null;
|
|
11327
|
+
let claim2 = null;
|
|
11089
11328
|
if (!utxos) {
|
|
11090
|
-
let claim2;
|
|
11091
11329
|
try {
|
|
11092
|
-
claim2 = await claimSpendableUtxos(config2.address, getUtxos, {
|
|
11330
|
+
claim2 = await claimSpendableUtxos(config2.address, getUtxos, {
|
|
11331
|
+
reconcile: reconcileHooks(spv_exports, readEntry),
|
|
11332
|
+
// ⚠️ AUDIT F2/F3 (wf_dda0064a): sizing the claim as ONE tx of the WHOLE payload was
|
|
11333
|
+
// wrong in both directions at once — TX_TOO_LARGE refused every >12.58MB payload the
|
|
11334
|
+
// chunker exists to SPLIT (a hard regression: each chunk tx is individually legal), and
|
|
11335
|
+
// when it did pass, the chain was UNDER-funded by construction (per-chunk envelopes +
|
|
11336
|
+
// per-tx overhead + the master tx were never budgeted; proven end-to-end: 1.84M sats
|
|
11337
|
+
// spent, no master tx, file unrestorable). Now the claim is sized for the CHAIN: the
|
|
11338
|
+
// first chunk as the tx shape, and every remaining fee — later chunks + the master —
|
|
11339
|
+
// as extraOutSats, which is exactly what the first tx's change must retain. TX_TOO_LARGE
|
|
11340
|
+
// then gates the CHUNK size, never the whole payload.
|
|
11341
|
+
spend: (() => {
|
|
11342
|
+
const E = Buffer.byteLength(String(encrypted || ""));
|
|
11343
|
+
if (E <= MAX_CHUNK_SIZE) return { dataBytes: E };
|
|
11344
|
+
const CHUNK_ENV = 400;
|
|
11345
|
+
const n = Math.ceil(E / MAX_CHUNK_SIZE);
|
|
11346
|
+
let restFees = 0;
|
|
11347
|
+
for (let i = 1; i < n; i++) {
|
|
11348
|
+
restFees += feeForInputs(1, { dataBytes: Math.min(MAX_CHUNK_SIZE, E - i * MAX_CHUNK_SIZE) + CHUNK_ENV });
|
|
11349
|
+
}
|
|
11350
|
+
restFees += feeForInputs(1, { dataBytes: 2e3 + n * 70 });
|
|
11351
|
+
return { dataBytes: Math.min(E, MAX_CHUNK_SIZE) + CHUNK_ENV, extraOutSats: restFees };
|
|
11352
|
+
})()
|
|
11353
|
+
});
|
|
11093
11354
|
} catch (e) {
|
|
11094
11355
|
if (e.code === "WALLET_BUSY_RESERVED") return { success: false, error: e.message, retryable: true };
|
|
11095
11356
|
throw e;
|
|
@@ -11099,7 +11360,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
11099
11360
|
}
|
|
11100
11361
|
if (!utxos || utxos.length === 0) {
|
|
11101
11362
|
if (heldClaims[0]) await settleReservation(heldClaims[0].token, "abort");
|
|
11102
|
-
return { success: false, error:
|
|
11363
|
+
return { success: false, error: claimRefusalMessage(claim2) };
|
|
11103
11364
|
}
|
|
11104
11365
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11105
11366
|
try {
|
|
@@ -11171,7 +11432,16 @@ async function saveFile(filePath, options = {}) {
|
|
|
11171
11432
|
if (!utxos || utxos.length === 0) {
|
|
11172
11433
|
await new Promise((r) => setTimeout(r, 500));
|
|
11173
11434
|
if (heldClaims.length) {
|
|
11174
|
-
const extra = await claimSpendableUtxos(config2.address, getUtxos
|
|
11435
|
+
const extra = await claimSpendableUtxos(config2.address, getUtxos, {
|
|
11436
|
+
// g-419: sized from the CURRENT chunk's exact payload — the right magnitude for
|
|
11437
|
+
// the next chunk it funds.
|
|
11438
|
+
spend: { dataBytes: Buffer.byteLength(chunkPayload) }
|
|
11439
|
+
});
|
|
11440
|
+
if (extra.refused || !extra.utxos || extra.utxos.length === 0) {
|
|
11441
|
+
const e = new Error(`INSUFFICIENT_FUNDS_MID_CHAIN: the wallet ran dry after ${i + 1} of ${chunks.length} chunks \u2014 ${claimRefusalMessage(extra)}`);
|
|
11442
|
+
e.code = "INSUFFICIENT_FUNDS_MID_CHAIN";
|
|
11443
|
+
throw e;
|
|
11444
|
+
}
|
|
11175
11445
|
heldClaims.push(extra);
|
|
11176
11446
|
utxos = extra.utxos;
|
|
11177
11447
|
} else {
|
|
@@ -12806,15 +13076,18 @@ async function _saveSessionInner(transcriptPath, summary, mode, hostBinding = nu
|
|
|
12806
13076
|
let memoryTxId = config2.memory_file_txid || null;
|
|
12807
13077
|
let historyTxId = config2.session_history_txid || null;
|
|
12808
13078
|
let changeUtxos = result.changeUtxos || [];
|
|
12809
|
-
if (config2.memory_auto_save !== false
|
|
13079
|
+
if (config2.memory_auto_save !== false) {
|
|
12810
13080
|
try {
|
|
12811
13081
|
const memoryDir = getMemoryDir(actualPath);
|
|
12812
13082
|
const memoryPath = join15(memoryDir, "MEMORY.md");
|
|
12813
13083
|
const historyPath = join15(memoryDir, "session-history.md");
|
|
13084
|
+
if (changeUtxos.length === 0) {
|
|
13085
|
+
process.stderr.write("[save_session] change folded to fee (g-419 dust backstop) \u2014 memory/history saves will claim their own coin\n");
|
|
13086
|
+
}
|
|
12814
13087
|
if (existsSync10(memoryPath)) {
|
|
12815
13088
|
const memResult = await saveFile(memoryPath, {
|
|
12816
13089
|
relativePath: "memory/MEMORY.md",
|
|
12817
|
-
utxos: changeUtxos,
|
|
13090
|
+
...changeUtxos.length > 0 ? { utxos: changeUtxos } : {},
|
|
12818
13091
|
skipVaultIndex: true
|
|
12819
13092
|
});
|
|
12820
13093
|
if (memResult.success) {
|
|
@@ -12822,10 +13095,10 @@ async function _saveSessionInner(transcriptPath, summary, mode, hostBinding = nu
|
|
|
12822
13095
|
changeUtxos = memResult.changeUtxos || [];
|
|
12823
13096
|
}
|
|
12824
13097
|
}
|
|
12825
|
-
if (existsSync10(historyPath)
|
|
13098
|
+
if (existsSync10(historyPath)) {
|
|
12826
13099
|
const histResult = await saveFile(historyPath, {
|
|
12827
13100
|
relativePath: "memory/session-history.md",
|
|
12828
|
-
utxos: changeUtxos,
|
|
13101
|
+
...changeUtxos.length > 0 ? { utxos: changeUtxos } : {},
|
|
12829
13102
|
skipVaultIndex: true
|
|
12830
13103
|
});
|
|
12831
13104
|
if (histResult.success) {
|
|
@@ -13199,7 +13472,7 @@ async function saveProject(dirPath, options = {}) {
|
|
|
13199
13472
|
};
|
|
13200
13473
|
let claim2;
|
|
13201
13474
|
try {
|
|
13202
|
-
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports, readEntry) });
|
|
13475
|
+
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports, readEntry), spend: { dataBytes: Buffer.byteLength(JSON.stringify(payload)) } });
|
|
13203
13476
|
} catch (e) {
|
|
13204
13477
|
if (e.code === "WALLET_BUSY_RESERVED") return { success: false, error: e.message, retryable: true };
|
|
13205
13478
|
throw e;
|
|
@@ -13208,7 +13481,7 @@ async function saveProject(dirPath, options = {}) {
|
|
|
13208
13481
|
const utxos = claim2.utxos;
|
|
13209
13482
|
if (!utxos || utxos.length === 0) {
|
|
13210
13483
|
await settleReservation(claim2.token, "abort");
|
|
13211
|
-
return { success: false, error:
|
|
13484
|
+
return { success: false, error: claimRefusalMessage(claim2) };
|
|
13212
13485
|
}
|
|
13213
13486
|
const bundleKey = sha256(bundleFiles.map((f) => f.content_hash).join(","));
|
|
13214
13487
|
const bc = await idempotentBuildAndBroadcast({
|
|
@@ -13748,7 +14021,7 @@ async function updateVaultIndex() {
|
|
|
13748
14021
|
};
|
|
13749
14022
|
let claim2 = null;
|
|
13750
14023
|
try {
|
|
13751
|
-
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports) });
|
|
14024
|
+
claim2 = await claimSpendableUtxos(config2.address, getUtxos, { reconcile: reconcileHooks(spv_exports), spend: { dataBytes: Buffer.byteLength(JSON.stringify(payload)) } });
|
|
13752
14025
|
} catch (e) {
|
|
13753
14026
|
if (e.code === "WALLET_BUSY_RESERVED") return { success: false, error: e.message, retryable: true };
|
|
13754
14027
|
throw e;
|
|
@@ -13757,7 +14030,7 @@ async function updateVaultIndex() {
|
|
|
13757
14030
|
let utxos = claim2.utxos;
|
|
13758
14031
|
if (!utxos || utxos.length === 0) {
|
|
13759
14032
|
await settleReservation(claim2.token, "abort");
|
|
13760
|
-
return { success: false, error:
|
|
14033
|
+
return { success: false, error: claimRefusalMessage(claim2) };
|
|
13761
14034
|
}
|
|
13762
14035
|
const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(
|
|
13763
14036
|
wif,
|
|
@@ -14275,7 +14548,13 @@ async function x402Fetch({ url, method = "GET", headers = {}, body, maxSats }) {
|
|
|
14275
14548
|
const address = privateKey.toPublicKey().toAddress();
|
|
14276
14549
|
let claim2;
|
|
14277
14550
|
try {
|
|
14278
|
-
claim2 = await claimSpendableUtxos(address, getUtxos, {
|
|
14551
|
+
claim2 = await claimSpendableUtxos(address, getUtxos, {
|
|
14552
|
+
reconcile: reconcileHooks(spv_exports),
|
|
14553
|
+
// ⚠️ SELECT AGAINST WHAT THIS CAN ACTUALLY PAY (codex-money 41657e3b). This route pays up
|
|
14554
|
+
// to `cap` sats to a third party via buildPaymentTx — a P2PKH payment output plus change,
|
|
14555
|
+
// no OP_RETURN. The exact shape, not a floor.
|
|
14556
|
+
spend: { dataBytes: 0, p2pkhOuts: 1, extraOutSats: cap }
|
|
14557
|
+
});
|
|
14279
14558
|
} catch (err9) {
|
|
14280
14559
|
if (err9.code === "WALLET_BUSY_RESERVED") return { success: false, error: err9.message, retryable: true };
|
|
14281
14560
|
return { success: false, error: `Failed to get UTXOs: ${err9.message}` };
|
|
@@ -14283,7 +14562,7 @@ async function x402Fetch({ url, method = "GET", headers = {}, body, maxSats }) {
|
|
|
14283
14562
|
const utxos = claim2.utxos;
|
|
14284
14563
|
if (!utxos || utxos.length === 0) {
|
|
14285
14564
|
await settleReservation(claim2.token, "abort");
|
|
14286
|
-
return { success: false, error:
|
|
14565
|
+
return { success: false, error: claimRefusalMessage(claim2) };
|
|
14287
14566
|
}
|
|
14288
14567
|
let txResult;
|
|
14289
14568
|
try {
|
|
@@ -14416,9 +14695,23 @@ async function saveGoalsToChain() {
|
|
|
14416
14695
|
const completed = goalsData.completed || [];
|
|
14417
14696
|
process.stderr.write(`[save_goals_to_chain] Bootstrapping ${goals.length} active + ${completed.length} completed goals to chain for ${address}...
|
|
14418
14697
|
`);
|
|
14698
|
+
const encrypted = encryptGoals(goalsData, wif);
|
|
14699
|
+
const payload = {
|
|
14700
|
+
protocol: "indelible.goals-snapshot",
|
|
14701
|
+
version: 1,
|
|
14702
|
+
owner: address,
|
|
14703
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14704
|
+
count: goals.length,
|
|
14705
|
+
completed_count: completed.length,
|
|
14706
|
+
encrypted
|
|
14707
|
+
};
|
|
14708
|
+
const snapshotAt = payload.timestamp;
|
|
14419
14709
|
let claim2;
|
|
14420
14710
|
try {
|
|
14421
|
-
claim2 = await claimSpendableUtxos(address, getUtxos, {
|
|
14711
|
+
claim2 = await claimSpendableUtxos(address, getUtxos, {
|
|
14712
|
+
reconcile: reconcileHooks(spv_exports),
|
|
14713
|
+
spend: { dataBytes: Buffer.byteLength(JSON.stringify(payload)) }
|
|
14714
|
+
});
|
|
14422
14715
|
} catch (e) {
|
|
14423
14716
|
if (e.code === "WALLET_BUSY_RESERVED") {
|
|
14424
14717
|
return { content: [{ type: "text", text: `${e.message}` }] };
|
|
@@ -14429,22 +14722,11 @@ async function saveGoalsToChain() {
|
|
|
14429
14722
|
if (!utxos || utxos.length === 0) {
|
|
14430
14723
|
await settleReservation(claim2.token, "abort");
|
|
14431
14724
|
return {
|
|
14432
|
-
content: [{ type: "text", text:
|
|
14725
|
+
content: [{ type: "text", text: claimRefusalMessage(claim2) }]
|
|
14433
14726
|
};
|
|
14434
14727
|
}
|
|
14435
|
-
let txId, fee, txSize, writeReceipt
|
|
14728
|
+
let txId, fee, txSize, writeReceipt;
|
|
14436
14729
|
try {
|
|
14437
|
-
const encrypted = encryptGoals(goalsData, wif);
|
|
14438
|
-
const payload = {
|
|
14439
|
-
protocol: "indelible.goals-snapshot",
|
|
14440
|
-
version: 1,
|
|
14441
|
-
owner: address,
|
|
14442
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14443
|
-
count: goals.length,
|
|
14444
|
-
completed_count: completed.length,
|
|
14445
|
-
encrypted
|
|
14446
|
-
};
|
|
14447
|
-
snapshotAt = payload.timestamp;
|
|
14448
14730
|
let txHex, changeUtxos;
|
|
14449
14731
|
({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(
|
|
14450
14732
|
wif,
|
|
@@ -17039,7 +17321,7 @@ Answer THAT message and nothing else \u2014 the wire also carries unrelated conv
|
|
|
17039
17321
|
}
|
|
17040
17322
|
function printHelp() {
|
|
17041
17323
|
console.log(`
|
|
17042
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v5.
|
|
17324
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v5.8.0)
|
|
17043
17325
|
|
|
17044
17326
|
Setup:
|
|
17045
17327
|
indelible-mcp Set up interactively (recommended \u2014 your key is never written to shell history)
|
|
@@ -17368,7 +17650,7 @@ function readStdin() {
|
|
|
17368
17650
|
}
|
|
17369
17651
|
var SERVER_INFO = {
|
|
17370
17652
|
name: "indelible",
|
|
17371
|
-
version: "5.
|
|
17653
|
+
version: "5.8.0",
|
|
17372
17654
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
17373
17655
|
};
|
|
17374
17656
|
var TOOLS = [
|