@cogcoin/client 1.1.4 → 1.1.5

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.
Files changed (83) hide show
  1. package/README.md +4 -5
  2. package/dist/bitcoind/progress/tty-renderer.js +3 -2
  3. package/dist/bitcoind/service.js +1 -1
  4. package/dist/cli/command-registry.d.ts +39 -0
  5. package/dist/cli/command-registry.js +1132 -0
  6. package/dist/cli/commands/client-admin.js +6 -56
  7. package/dist/cli/commands/mining-admin.js +9 -32
  8. package/dist/cli/commands/mining-read.js +15 -56
  9. package/dist/cli/commands/mining-runtime.js +258 -57
  10. package/dist/cli/commands/service-runtime.js +1 -64
  11. package/dist/cli/commands/status.js +2 -15
  12. package/dist/cli/commands/update.js +6 -21
  13. package/dist/cli/commands/wallet-admin.js +18 -120
  14. package/dist/cli/commands/wallet-mutation.js +4 -7
  15. package/dist/cli/commands/wallet-read.js +31 -138
  16. package/dist/cli/context.js +2 -4
  17. package/dist/cli/mining-format.js +8 -2
  18. package/dist/cli/mutation-command-groups.d.ts +11 -11
  19. package/dist/cli/mutation-command-groups.js +9 -18
  20. package/dist/cli/mutation-json.d.ts +1 -17
  21. package/dist/cli/mutation-json.js +1 -28
  22. package/dist/cli/mutation-success.d.ts +0 -1
  23. package/dist/cli/mutation-success.js +0 -19
  24. package/dist/cli/output.d.ts +1 -10
  25. package/dist/cli/output.js +52 -481
  26. package/dist/cli/parse.d.ts +1 -1
  27. package/dist/cli/parse.js +38 -695
  28. package/dist/cli/runner.js +28 -113
  29. package/dist/cli/types.d.ts +7 -8
  30. package/dist/cli/update-notifier.js +1 -1
  31. package/dist/cli/wallet-format.js +1 -1
  32. package/dist/wallet/lifecycle/managed-core.d.ts +23 -0
  33. package/dist/wallet/lifecycle/managed-core.js +257 -0
  34. package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
  35. package/dist/wallet/lifecycle/repair-mining.js +304 -0
  36. package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
  37. package/dist/wallet/lifecycle/repair-runtime.js +206 -0
  38. package/dist/wallet/lifecycle/repair.d.ts +11 -0
  39. package/dist/wallet/lifecycle/repair.js +368 -0
  40. package/dist/wallet/lifecycle/setup.d.ts +16 -0
  41. package/dist/wallet/lifecycle/setup.js +430 -0
  42. package/dist/wallet/lifecycle/types.d.ts +125 -0
  43. package/dist/wallet/lifecycle/types.js +1 -0
  44. package/dist/wallet/lifecycle.d.ts +4 -165
  45. package/dist/wallet/lifecycle.js +3 -1656
  46. package/dist/wallet/mining/candidate.d.ts +60 -0
  47. package/dist/wallet/mining/candidate.js +290 -0
  48. package/dist/wallet/mining/competitiveness.d.ts +22 -0
  49. package/dist/wallet/mining/competitiveness.js +640 -0
  50. package/dist/wallet/mining/control.js +7 -251
  51. package/dist/wallet/mining/cycle.d.ts +39 -0
  52. package/dist/wallet/mining/cycle.js +542 -0
  53. package/dist/wallet/mining/engine-state.d.ts +66 -0
  54. package/dist/wallet/mining/engine-state.js +211 -0
  55. package/dist/wallet/mining/engine-types.d.ts +173 -0
  56. package/dist/wallet/mining/engine-types.js +1 -0
  57. package/dist/wallet/mining/engine-utils.d.ts +7 -0
  58. package/dist/wallet/mining/engine-utils.js +75 -0
  59. package/dist/wallet/mining/events.d.ts +2 -0
  60. package/dist/wallet/mining/events.js +19 -0
  61. package/dist/wallet/mining/lifecycle.d.ts +71 -0
  62. package/dist/wallet/mining/lifecycle.js +355 -0
  63. package/dist/wallet/mining/projection.d.ts +61 -0
  64. package/dist/wallet/mining/projection.js +319 -0
  65. package/dist/wallet/mining/publish.d.ts +79 -0
  66. package/dist/wallet/mining/publish.js +614 -0
  67. package/dist/wallet/mining/runner.d.ts +12 -418
  68. package/dist/wallet/mining/runner.js +274 -3433
  69. package/dist/wallet/mining/supervisor.d.ts +134 -0
  70. package/dist/wallet/mining/supervisor.js +558 -0
  71. package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
  72. package/dist/wallet/mining/visualizer-sync.js +166 -0
  73. package/dist/wallet/mining/visualizer.d.ts +1 -0
  74. package/dist/wallet/mining/visualizer.js +33 -18
  75. package/dist/wallet/reset.d.ts +1 -1
  76. package/dist/wallet/reset.js +35 -11
  77. package/dist/wallet/runtime.d.ts +0 -6
  78. package/dist/wallet/runtime.js +2 -38
  79. package/dist/wallet/tx/common.d.ts +18 -0
  80. package/dist/wallet/tx/common.js +40 -26
  81. package/package.json +1 -1
  82. package/dist/wallet/state/seed-index.d.ts +0 -43
  83. package/dist/wallet/state/seed-index.js +0 -151
@@ -0,0 +1,640 @@
1
+ import { createHash } from "node:crypto";
2
+ import { assaySentences, deriveBlendSeed } from "@cogcoin/scoring";
3
+ import { lookupDomain, lookupDomainById } from "@cogcoin/indexer/queries";
4
+ import { COG_OPCODES, COG_PREFIX } from "../cogop/constants.js";
5
+ import { extractOpReturnPayloadFromScriptHex } from "../tx/register.js";
6
+ import { compareLexicographically, numberToSats, resolveBip39WordsFromIndices, rootDomain, tieBreakHash, } from "./engine-utils.js";
7
+ import { getIndexerTruthKey } from "./candidate.js";
8
+ const MINING_MEMPOOL_COOPERATIVE_YIELD_EVERY = 25;
9
+ const miningGateCache = new Map();
10
+ function defaultMiningCooperativeYield() {
11
+ return new Promise((resolve) => {
12
+ setImmediate(resolve);
13
+ });
14
+ }
15
+ async function maybeYieldDuringMempoolScan(options) {
16
+ const yieldEvery = options.cooperativeYieldEvery ?? MINING_MEMPOOL_COOPERATIVE_YIELD_EVERY;
17
+ if (yieldEvery <= 0 || options.iteration === 0 || (options.iteration % yieldEvery) !== 0) {
18
+ return;
19
+ }
20
+ await (options.cooperativeYield ?? defaultMiningCooperativeYield)();
21
+ }
22
+ export function clearMiningGateCache(walletRootId) {
23
+ if (walletRootId === null || walletRootId === undefined) {
24
+ miningGateCache.clear();
25
+ return;
26
+ }
27
+ miningGateCache.delete(walletRootId);
28
+ }
29
+ function decodeMinePayload(payload) {
30
+ if (payload.length < 68 || Buffer.from(payload.subarray(0, 3)).toString("utf8") !== "COG" || payload[3] !== 0x01) {
31
+ return null;
32
+ }
33
+ return {
34
+ domainId: Buffer.from(payload).readUInt32BE(4),
35
+ referencedBlockPrefixHex: Buffer.from(payload.subarray(8, 12)).toString("hex"),
36
+ sentenceBytes: payload.subarray(12, 72),
37
+ };
38
+ }
39
+ function bytesToHex(value) {
40
+ return value == null ? null : Buffer.from(value).toString("hex");
41
+ }
42
+ function readU32BE(bytes, offset) {
43
+ if ((offset + 4) > bytes.length) {
44
+ return null;
45
+ }
46
+ return Buffer.from(bytes.subarray(offset, offset + 4)).readUInt32BE(0);
47
+ }
48
+ function readLenPrefixedScriptHex(bytes, offset) {
49
+ const length = bytes[offset];
50
+ if (length === undefined || (offset + 1 + length) > bytes.length) {
51
+ return null;
52
+ }
53
+ return {
54
+ scriptHex: Buffer.from(bytes.subarray(offset + 1, offset + 1 + length)).toString("hex"),
55
+ nextOffset: offset + 1 + length,
56
+ };
57
+ }
58
+ function parseSupportedAncestorOperation(context) {
59
+ const payload = context.payload;
60
+ if (payload === null) {
61
+ return null;
62
+ }
63
+ if (payload.length < 4
64
+ || payload[0] !== COG_PREFIX[0]
65
+ || payload[1] !== COG_PREFIX[1]
66
+ || payload[2] !== COG_PREFIX[2]) {
67
+ return null;
68
+ }
69
+ const opcode = payload[3];
70
+ if (opcode === COG_OPCODES.DOMAIN_REG) {
71
+ const nameLength = payload[4];
72
+ if (nameLength === undefined || (5 + nameLength) !== payload.length) {
73
+ return "unsupported";
74
+ }
75
+ return {
76
+ kind: "domain-reg",
77
+ name: Buffer.from(payload.subarray(5, 5 + nameLength)).toString("utf8"),
78
+ senderScriptHex: context.senderScriptHex,
79
+ };
80
+ }
81
+ if (opcode === COG_OPCODES.DOMAIN_TRANSFER) {
82
+ const domainId = readU32BE(payload, 4);
83
+ const recipient = domainId === null ? null : readLenPrefixedScriptHex(payload, 8);
84
+ if (domainId === null || recipient === null || recipient.nextOffset !== payload.length) {
85
+ return "unsupported";
86
+ }
87
+ return {
88
+ kind: "domain-transfer",
89
+ domainId,
90
+ recipientScriptHex: recipient.scriptHex,
91
+ senderScriptHex: context.senderScriptHex,
92
+ };
93
+ }
94
+ if (opcode === COG_OPCODES.DOMAIN_ANCHOR) {
95
+ const domainId = readU32BE(payload, 4);
96
+ if (domainId === null) {
97
+ return "unsupported";
98
+ }
99
+ return {
100
+ kind: "domain-anchor",
101
+ domainId,
102
+ senderScriptHex: context.senderScriptHex,
103
+ };
104
+ }
105
+ if (opcode === COG_OPCODES.SET_DELEGATE || opcode === COG_OPCODES.SET_MINER) {
106
+ const domainId = readU32BE(payload, 4);
107
+ if (domainId === null) {
108
+ return "unsupported";
109
+ }
110
+ if (payload.length === 8) {
111
+ return opcode === COG_OPCODES.SET_DELEGATE
112
+ ? { kind: "set-delegate", domainId, delegateScriptHex: null }
113
+ : { kind: "set-miner", domainId, minerScriptHex: null };
114
+ }
115
+ const target = readLenPrefixedScriptHex(payload, 8);
116
+ if (target === null || target.nextOffset !== payload.length) {
117
+ return "unsupported";
118
+ }
119
+ return opcode === COG_OPCODES.SET_DELEGATE
120
+ ? { kind: "set-delegate", domainId, delegateScriptHex: target.scriptHex }
121
+ : { kind: "set-miner", domainId, minerScriptHex: target.scriptHex };
122
+ }
123
+ return "unsupported";
124
+ }
125
+ function getAncestorTxids(context, txContexts) {
126
+ return context.rawTransaction.vin
127
+ .map((vin) => vin.txid ?? null)
128
+ .filter((txid) => txid !== null && txContexts.has(txid));
129
+ }
130
+ function topologicallyOrderAncestorContexts(options) {
131
+ const visited = new Map();
132
+ const ordered = [];
133
+ const root = options.txContexts.get(options.txid);
134
+ if (root === undefined) {
135
+ return [];
136
+ }
137
+ const stack = getAncestorTxids(root, options.txContexts)
138
+ .reverse()
139
+ .map((txid) => ({
140
+ txid,
141
+ expanded: false,
142
+ }));
143
+ while (stack.length > 0) {
144
+ const frame = stack.pop();
145
+ const state = visited.get(frame.txid);
146
+ if (frame.expanded) {
147
+ if (state !== "visiting") {
148
+ continue;
149
+ }
150
+ visited.set(frame.txid, "visited");
151
+ const context = options.txContexts.get(frame.txid);
152
+ if (context !== undefined) {
153
+ ordered.push(context);
154
+ }
155
+ continue;
156
+ }
157
+ if (state === "visited") {
158
+ continue;
159
+ }
160
+ if (state === "visiting") {
161
+ return null;
162
+ }
163
+ const context = options.txContexts.get(frame.txid);
164
+ if (context === undefined) {
165
+ continue;
166
+ }
167
+ visited.set(frame.txid, "visiting");
168
+ stack.push({
169
+ txid: frame.txid,
170
+ expanded: true,
171
+ });
172
+ const parents = getAncestorTxids(context, options.txContexts);
173
+ for (let index = parents.length - 1; index >= 0; index -= 1) {
174
+ const parentTxid = parents[index];
175
+ const parentState = visited.get(parentTxid);
176
+ if (parentState === "visiting") {
177
+ return null;
178
+ }
179
+ if (parentState !== "visited") {
180
+ stack.push({
181
+ txid: parentTxid,
182
+ expanded: false,
183
+ });
184
+ }
185
+ }
186
+ }
187
+ return ordered;
188
+ }
189
+ export function topologicallyOrderAncestorTxidsForTesting(options) {
190
+ const ordered = topologicallyOrderAncestorContexts({
191
+ txid: options.txid,
192
+ txContexts: options.txContexts,
193
+ });
194
+ return ordered?.map((context) => context.txid) ?? null;
195
+ }
196
+ function cloneOverlayDomainFromConfirmed(readContext, domainId) {
197
+ const domain = lookupDomainById(readContext.snapshot.state, domainId);
198
+ if (domain === null) {
199
+ return null;
200
+ }
201
+ return {
202
+ domainId,
203
+ name: domain.name,
204
+ anchored: domain.anchored,
205
+ ownerScriptHex: bytesToHex(domain.ownerScriptPubKey),
206
+ delegateScriptHex: bytesToHex(domain.delegate),
207
+ minerScriptHex: bytesToHex(domain.miner),
208
+ };
209
+ }
210
+ function applySupportedAncestorOperation(options) {
211
+ const ensureDomain = (domainId) => {
212
+ const existing = options.overlay.get(domainId);
213
+ if (existing !== undefined) {
214
+ return existing;
215
+ }
216
+ const confirmed = cloneOverlayDomainFromConfirmed(options.readContext, domainId);
217
+ if (confirmed === null) {
218
+ return null;
219
+ }
220
+ options.overlay.set(domainId, confirmed);
221
+ return confirmed;
222
+ };
223
+ if (options.operation.kind === "domain-reg") {
224
+ if (!rootDomain(options.operation.name)) {
225
+ return { nextDomainId: options.nextDomainId, indeterminate: true };
226
+ }
227
+ if (lookupDomain(options.readContext.snapshot.state, options.operation.name) !== null) {
228
+ return { nextDomainId: options.nextDomainId, indeterminate: true };
229
+ }
230
+ options.overlay.set(options.nextDomainId, {
231
+ domainId: options.nextDomainId,
232
+ name: options.operation.name,
233
+ anchored: false,
234
+ ownerScriptHex: options.operation.senderScriptHex,
235
+ delegateScriptHex: null,
236
+ minerScriptHex: null,
237
+ });
238
+ return {
239
+ nextDomainId: options.nextDomainId + 1,
240
+ indeterminate: false,
241
+ };
242
+ }
243
+ const domain = ensureDomain(options.operation.domainId);
244
+ if (domain === null) {
245
+ return { nextDomainId: options.nextDomainId, indeterminate: true };
246
+ }
247
+ if (options.operation.kind === "domain-transfer") {
248
+ domain.ownerScriptHex = options.operation.recipientScriptHex;
249
+ options.overlay.set(domain.domainId, domain);
250
+ return { nextDomainId: options.nextDomainId, indeterminate: false };
251
+ }
252
+ if (options.operation.kind === "domain-anchor") {
253
+ domain.anchored = true;
254
+ if (options.operation.senderScriptHex !== null) {
255
+ domain.ownerScriptHex = options.operation.senderScriptHex;
256
+ }
257
+ options.overlay.set(domain.domainId, domain);
258
+ return { nextDomainId: options.nextDomainId, indeterminate: false };
259
+ }
260
+ if (options.operation.kind === "set-delegate") {
261
+ domain.delegateScriptHex = options.operation.delegateScriptHex;
262
+ options.overlay.set(domain.domainId, domain);
263
+ return { nextDomainId: options.nextDomainId, indeterminate: false };
264
+ }
265
+ domain.minerScriptHex = options.operation.minerScriptHex;
266
+ options.overlay.set(domain.domainId, domain);
267
+ return { nextDomainId: options.nextDomainId, indeterminate: false };
268
+ }
269
+ async function resolveOverlayAuthorizedMiningDomain(options) {
270
+ const orderedAncestors = topologicallyOrderAncestorContexts({
271
+ txid: options.txid,
272
+ txContexts: options.txContexts,
273
+ });
274
+ if (orderedAncestors === null) {
275
+ return "indeterminate";
276
+ }
277
+ const overlay = new Map();
278
+ let nextDomainId = options.readContext.snapshot.state.consensus.nextDomainId;
279
+ for (const ancestor of orderedAncestors) {
280
+ const parsed = parseSupportedAncestorOperation(ancestor);
281
+ if (parsed === "unsupported") {
282
+ return "indeterminate";
283
+ }
284
+ if (parsed === null) {
285
+ continue;
286
+ }
287
+ const applied = applySupportedAncestorOperation({
288
+ readContext: options.readContext,
289
+ overlay,
290
+ nextDomainId,
291
+ operation: parsed,
292
+ });
293
+ nextDomainId = applied.nextDomainId;
294
+ if (applied.indeterminate) {
295
+ return "indeterminate";
296
+ }
297
+ }
298
+ const domain = overlay.get(options.domainId) ?? cloneOverlayDomainFromConfirmed(options.readContext, options.domainId);
299
+ if (domain === null || domain.name === null || !rootDomain(domain.name) || !domain.anchored) {
300
+ return null;
301
+ }
302
+ const authorized = domain.ownerScriptHex === options.senderScriptHex
303
+ || domain.delegateScriptHex === options.senderScriptHex
304
+ || domain.minerScriptHex === options.senderScriptHex;
305
+ return authorized ? domain : null;
306
+ }
307
+ function isBetterVisibleCompetitor(candidate, current) {
308
+ if (current === undefined) {
309
+ return true;
310
+ }
311
+ if (candidate.canonicalBlend !== current.canonicalBlend) {
312
+ return candidate.canonicalBlend > current.canonicalBlend;
313
+ }
314
+ if (candidate.effectiveFeeRate !== current.effectiveFeeRate) {
315
+ return candidate.effectiveFeeRate > current.effectiveFeeRate;
316
+ }
317
+ return candidate.txid.localeCompare(current.txid) < 0;
318
+ }
319
+ function rankMiningSentenceEntries(entries, blendSeed) {
320
+ return entries
321
+ .map((entry) => ({
322
+ ...entry,
323
+ tieBreak: tieBreakHash(blendSeed, entry.domainId),
324
+ }))
325
+ .sort((left, right) => {
326
+ if (left.canonicalBlend !== right.canonicalBlend) {
327
+ return left.canonicalBlend > right.canonicalBlend ? -1 : 1;
328
+ }
329
+ const tieBreakOrder = compareLexicographically(left.tieBreak, right.tieBreak);
330
+ if (tieBreakOrder !== 0) {
331
+ return tieBreakOrder;
332
+ }
333
+ return left.txIndex - right.txIndex;
334
+ })
335
+ .map((entry, index) => ({
336
+ ...entry,
337
+ rank: index + 1,
338
+ }));
339
+ }
340
+ function toSentenceBoardEntries(entries) {
341
+ return entries.slice(0, 5).map((entry) => ({
342
+ rank: entry.rank,
343
+ domainName: entry.domainName,
344
+ sentence: entry.sentence,
345
+ requiredWords: resolveBip39WordsFromIndices(entry.bip39WordIndices),
346
+ }));
347
+ }
348
+ export async function runCompetitivenessGate(options) {
349
+ const createDecision = (overrides) => ({
350
+ allowed: overrides.allowed ?? false,
351
+ decision: overrides.decision ?? "indeterminate-mempool-gate",
352
+ sameDomainCompetitorSuppressed: overrides.sameDomainCompetitorSuppressed ?? false,
353
+ higherRankedCompetitorDomainCount: overrides.higherRankedCompetitorDomainCount ?? 0,
354
+ dedupedCompetitorDomainCount: overrides.dedupedCompetitorDomainCount ?? 0,
355
+ competitivenessGateIndeterminate: overrides.competitivenessGateIndeterminate ?? false,
356
+ mempoolSequenceCacheStatus: overrides.mempoolSequenceCacheStatus ?? null,
357
+ lastMempoolSequence: overrides.lastMempoolSequence ?? null,
358
+ visibleBoardEntries: overrides.visibleBoardEntries ?? [],
359
+ candidateRank: overrides.candidateRank ?? null,
360
+ });
361
+ const walletRootId = options.readContext.localState.walletRootId ?? "uninitialized-wallet-root";
362
+ const assaySentencesImpl = options.assaySentencesImpl ?? assaySentences;
363
+ const indexerTruthKey = getIndexerTruthKey(options.readContext);
364
+ const excludedTxids = [options.currentTxid].filter((value) => value !== null).sort();
365
+ const localAssayTupleKey = [
366
+ options.candidate.domainId,
367
+ Buffer.from(options.candidate.encodedSentenceBytes).toString("hex"),
368
+ options.candidate.canonicalBlend.toString(),
369
+ options.candidate.sender.scriptPubKeyHex,
370
+ ].join(":");
371
+ let mempoolVerbose;
372
+ try {
373
+ mempoolVerbose = await options.rpc.getRawMempoolVerbose();
374
+ }
375
+ catch {
376
+ return createDecision({
377
+ competitivenessGateIndeterminate: true,
378
+ });
379
+ }
380
+ const mempoolSequence = String(mempoolVerbose.mempool_sequence);
381
+ const cached = miningGateCache.get(walletRootId);
382
+ const cachedTruthMatches = cached !== undefined
383
+ && indexerTruthKey !== null
384
+ && cached.indexerDaemonInstanceId === indexerTruthKey.daemonInstanceId
385
+ && cached.indexerSnapshotSeq === indexerTruthKey.snapshotSeq;
386
+ const cachedReferencedBlockMatches = cached !== undefined
387
+ && cached.referencedBlockHashDisplay === options.candidate.referencedBlockHashDisplay;
388
+ if (cached !== undefined && (!cachedTruthMatches || !cachedReferencedBlockMatches)) {
389
+ clearMiningGateCache(walletRootId);
390
+ }
391
+ if (cached !== undefined
392
+ && cachedTruthMatches
393
+ && cachedReferencedBlockMatches
394
+ && cached.localAssayTupleKey === localAssayTupleKey
395
+ && cached.excludedTxidsKey === excludedTxids.join(",")
396
+ && cached.mempoolSequence === mempoolSequence) {
397
+ return {
398
+ ...cached.decision,
399
+ mempoolSequenceCacheStatus: "reused",
400
+ };
401
+ }
402
+ const referencedPrefix = Buffer.from(options.candidate.referencedBlockHashInternal.subarray(0, 4)).toString("hex");
403
+ const visibleTxids = mempoolVerbose.txids.filter((txid) => !excludedTxids.includes(txid));
404
+ const txContexts = cachedTruthMatches && cachedReferencedBlockMatches
405
+ ? (cached?.txContexts ?? new Map())
406
+ : new Map();
407
+ for (const txid of [...txContexts.keys()]) {
408
+ if (!visibleTxids.includes(txid)) {
409
+ txContexts.delete(txid);
410
+ }
411
+ }
412
+ for (let index = 0; index < visibleTxids.length; index += 1) {
413
+ await maybeYieldDuringMempoolScan({
414
+ iteration: index,
415
+ cooperativeYield: options.cooperativeYield,
416
+ cooperativeYieldEvery: options.cooperativeYieldEvery,
417
+ });
418
+ const txid = visibleTxids[index];
419
+ if (txContexts.has(txid)) {
420
+ continue;
421
+ }
422
+ const [tx, mempoolEntry] = await Promise.all([
423
+ options.rpc.getRawTransaction(txid, true).catch(() => null),
424
+ options.rpc.getMempoolEntry(txid).catch(() => null),
425
+ ]);
426
+ if (tx === null || mempoolEntry === null) {
427
+ continue;
428
+ }
429
+ const effectiveFeeRate = Number([
430
+ mempoolEntry.vsize > 0 ? (numberToSats(mempoolEntry.fees.base) / BigInt(mempoolEntry.vsize)) : 0n,
431
+ (mempoolEntry.ancestorsize ?? 0) > 0 ? (numberToSats(mempoolEntry.fees.ancestor) / BigInt(mempoolEntry.ancestorsize ?? 1)) : 0n,
432
+ (mempoolEntry.descendantsize ?? 0) > 0 ? (numberToSats(mempoolEntry.fees.descendant) / BigInt(mempoolEntry.descendantsize ?? 1)) : 0n,
433
+ ].reduce((best, candidate) => (candidate > best ? candidate : best), 0n));
434
+ const payloadHex = tx.vout.find((entry) => entry.scriptPubKey?.hex?.startsWith("6a") === true)?.scriptPubKey?.hex;
435
+ txContexts.set(txid, {
436
+ txid,
437
+ effectiveFeeRate,
438
+ senderScriptHex: tx.vin[0]?.prevout?.scriptPubKey?.hex ?? null,
439
+ rawTransaction: tx,
440
+ payload: payloadHex === undefined ? null : extractOpReturnPayloadFromScriptHex(payloadHex),
441
+ });
442
+ }
443
+ const entries = new Map();
444
+ for (let index = 0; index < visibleTxids.length; index += 1) {
445
+ await maybeYieldDuringMempoolScan({
446
+ iteration: index,
447
+ cooperativeYield: options.cooperativeYield,
448
+ cooperativeYieldEvery: options.cooperativeYieldEvery,
449
+ });
450
+ const txid = visibleTxids[index];
451
+ const context = txContexts.get(txid);
452
+ if (context === undefined || context.payload === null || context.senderScriptHex === null) {
453
+ continue;
454
+ }
455
+ const decoded = decodeMinePayload(context.payload);
456
+ if (decoded === null || decoded.referencedBlockPrefixHex !== referencedPrefix) {
457
+ continue;
458
+ }
459
+ const overlayDomain = await resolveOverlayAuthorizedMiningDomain({
460
+ readContext: options.readContext,
461
+ txid,
462
+ txContexts,
463
+ domainId: decoded.domainId,
464
+ senderScriptHex: context.senderScriptHex,
465
+ });
466
+ if (overlayDomain === "indeterminate") {
467
+ const decision = createDecision({
468
+ competitivenessGateIndeterminate: true,
469
+ decision: "indeterminate-mempool-gate",
470
+ mempoolSequenceCacheStatus: "refreshed",
471
+ lastMempoolSequence: mempoolSequence,
472
+ });
473
+ miningGateCache.set(walletRootId, {
474
+ indexerDaemonInstanceId: indexerTruthKey?.daemonInstanceId ?? "none",
475
+ indexerSnapshotSeq: indexerTruthKey?.snapshotSeq ?? "none",
476
+ referencedBlockHashDisplay: options.candidate.referencedBlockHashDisplay,
477
+ localAssayTupleKey,
478
+ excludedTxidsKey: excludedTxids.join(","),
479
+ mempoolSequence,
480
+ txids: [...visibleTxids],
481
+ txContexts,
482
+ decision,
483
+ });
484
+ return decision;
485
+ }
486
+ if (overlayDomain === null || overlayDomain.name === null || !rootDomain(overlayDomain.name)) {
487
+ continue;
488
+ }
489
+ const assayed = await assaySentencesImpl(decoded.domainId, options.candidate.referencedBlockHashInternal, [Buffer.from(decoded.sentenceBytes).toString("utf8")]).catch(() => []);
490
+ const scored = assayed[0];
491
+ if (scored === undefined || !scored.gatesPass || scored.encodedSentenceBytes === null || scored.canonicalBlend === null) {
492
+ continue;
493
+ }
494
+ entries.set(txid, {
495
+ txid,
496
+ effectiveFeeRate: context.effectiveFeeRate,
497
+ domainId: decoded.domainId,
498
+ domainName: overlayDomain.name,
499
+ sentence: Buffer.from(decoded.sentenceBytes).toString("utf8"),
500
+ senderScriptHex: context.senderScriptHex,
501
+ encodedSentenceBytesHex: Buffer.from(scored.encodedSentenceBytes).toString("hex"),
502
+ bip39WordIndices: [...scored.bip39WordIndices],
503
+ canonicalBlend: scored.canonicalBlend,
504
+ });
505
+ }
506
+ const blendSeed = deriveBlendSeed(options.candidate.referencedBlockHashInternal);
507
+ const visibleBestByDomain = new Map();
508
+ for (const entry of entries.values()) {
509
+ const current = visibleBestByDomain.get(entry.domainId);
510
+ if (isBetterVisibleCompetitor(entry, current)) {
511
+ visibleBestByDomain.set(entry.domainId, entry);
512
+ }
513
+ }
514
+ const visibleRankedEntries = rankMiningSentenceEntries([...visibleBestByDomain.values()]
515
+ .sort((left, right) => left.domainId - right.domainId || left.txid.localeCompare(right.txid))
516
+ .map((entry, index) => ({
517
+ domainId: entry.domainId,
518
+ domainName: entry.domainName,
519
+ sentence: entry.sentence,
520
+ canonicalBlend: entry.canonicalBlend,
521
+ senderScriptHex: entry.senderScriptHex,
522
+ encodedSentenceBytesHex: entry.encodedSentenceBytesHex,
523
+ bip39WordIndices: entry.bip39WordIndices,
524
+ txid: entry.txid,
525
+ txIndex: index,
526
+ })), blendSeed);
527
+ const sameDomainCompetitors = [...visibleBestByDomain.values()].filter((entry) => entry.domainId === options.candidate.domainId);
528
+ const sameDomainCompetitorSuppressed = sameDomainCompetitors.some((competitor) => competitor.canonicalBlend > options.candidate.canonicalBlend
529
+ || competitor.canonicalBlend === options.candidate.canonicalBlend);
530
+ let decision;
531
+ const otherDomainBest = new Map();
532
+ for (const entry of visibleBestByDomain.values()) {
533
+ if (entry.domainId === options.candidate.domainId) {
534
+ continue;
535
+ }
536
+ const best = otherDomainBest.get(entry.domainId);
537
+ if (isBetterVisibleCompetitor(entry, best)) {
538
+ otherDomainBest.set(entry.domainId, entry);
539
+ }
540
+ }
541
+ if (sameDomainCompetitorSuppressed) {
542
+ decision = createDecision({
543
+ allowed: false,
544
+ decision: "suppressed-same-domain-mempool",
545
+ sameDomainCompetitorSuppressed: true,
546
+ higherRankedCompetitorDomainCount: 1,
547
+ dedupedCompetitorDomainCount: otherDomainBest.size,
548
+ competitivenessGateIndeterminate: false,
549
+ mempoolSequenceCacheStatus: "refreshed",
550
+ lastMempoolSequence: mempoolSequence,
551
+ visibleBoardEntries: toSentenceBoardEntries(visibleRankedEntries),
552
+ });
553
+ }
554
+ else {
555
+ try {
556
+ const candidateRankedEntries = rankMiningSentenceEntries([
557
+ {
558
+ domainId: options.candidate.domainId,
559
+ domainName: options.candidate.domainName,
560
+ sentence: options.candidate.sentence,
561
+ canonicalBlend: options.candidate.canonicalBlend,
562
+ senderScriptHex: options.candidate.sender.scriptPubKeyHex,
563
+ encodedSentenceBytesHex: Buffer.from(options.candidate.encodedSentenceBytes).toString("hex"),
564
+ bip39WordIndices: options.candidate.bip39WordIndices,
565
+ txid: null,
566
+ txIndex: 0,
567
+ },
568
+ ...[...otherDomainBest.values()]
569
+ .sort((left, right) => left.domainId - right.domainId || left.txid.localeCompare(right.txid))
570
+ .map((entry, index) => ({
571
+ domainId: entry.domainId,
572
+ domainName: entry.domainName,
573
+ sentence: entry.sentence,
574
+ canonicalBlend: entry.canonicalBlend,
575
+ senderScriptHex: entry.senderScriptHex,
576
+ encodedSentenceBytesHex: entry.encodedSentenceBytesHex,
577
+ bip39WordIndices: entry.bip39WordIndices,
578
+ txid: entry.txid,
579
+ txIndex: index + 1,
580
+ })),
581
+ ], blendSeed);
582
+ const localEntry = candidateRankedEntries.find((entry) => entry.txid === null) ?? null;
583
+ const candidateRank = localEntry?.rank ?? null;
584
+ const higherRankedCompetitorDomainCount = candidateRank === null ? 0 : Math.max(0, candidateRank - 1);
585
+ if (candidateRank !== null && candidateRank > 5) {
586
+ decision = createDecision({
587
+ allowed: false,
588
+ decision: "suppressed-top5-mempool",
589
+ sameDomainCompetitorSuppressed: false,
590
+ higherRankedCompetitorDomainCount,
591
+ dedupedCompetitorDomainCount: otherDomainBest.size,
592
+ competitivenessGateIndeterminate: false,
593
+ mempoolSequenceCacheStatus: "refreshed",
594
+ lastMempoolSequence: mempoolSequence,
595
+ visibleBoardEntries: toSentenceBoardEntries(visibleRankedEntries),
596
+ candidateRank,
597
+ });
598
+ }
599
+ else {
600
+ decision = createDecision({
601
+ allowed: candidateRank !== null,
602
+ decision: "publish",
603
+ sameDomainCompetitorSuppressed: false,
604
+ higherRankedCompetitorDomainCount,
605
+ dedupedCompetitorDomainCount: otherDomainBest.size,
606
+ competitivenessGateIndeterminate: false,
607
+ mempoolSequenceCacheStatus: "refreshed",
608
+ lastMempoolSequence: mempoolSequence,
609
+ visibleBoardEntries: toSentenceBoardEntries(visibleRankedEntries),
610
+ candidateRank,
611
+ });
612
+ }
613
+ }
614
+ catch {
615
+ decision = createDecision({
616
+ allowed: false,
617
+ decision: "indeterminate-mempool-gate",
618
+ sameDomainCompetitorSuppressed: false,
619
+ higherRankedCompetitorDomainCount: 0,
620
+ dedupedCompetitorDomainCount: otherDomainBest.size,
621
+ competitivenessGateIndeterminate: true,
622
+ mempoolSequenceCacheStatus: "refreshed",
623
+ lastMempoolSequence: mempoolSequence,
624
+ visibleBoardEntries: toSentenceBoardEntries(visibleRankedEntries),
625
+ });
626
+ }
627
+ }
628
+ miningGateCache.set(walletRootId, {
629
+ indexerDaemonInstanceId: indexerTruthKey?.daemonInstanceId ?? "none",
630
+ indexerSnapshotSeq: indexerTruthKey?.snapshotSeq ?? "none",
631
+ referencedBlockHashDisplay: options.candidate.referencedBlockHashDisplay,
632
+ localAssayTupleKey,
633
+ excludedTxidsKey: excludedTxids.join(","),
634
+ mempoolSequence,
635
+ txids: [...visibleTxids],
636
+ txContexts,
637
+ decision,
638
+ });
639
+ return decision;
640
+ }