@cogcoin/client 1.1.6 → 1.1.8

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 (125) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.js +29 -79
  3. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  4. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  7. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  8. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  9. package/dist/bitcoind/managed-runtime/types.d.ts +40 -0
  10. package/dist/bitcoind/node.d.ts +2 -2
  11. package/dist/bitcoind/node.js +2 -2
  12. package/dist/bitcoind/rpc.d.ts +2 -1
  13. package/dist/bitcoind/rpc.js +53 -3
  14. package/dist/bitcoind/service.js +47 -127
  15. package/dist/cli/command-registry.d.ts +1 -1
  16. package/dist/cli/command-registry.js +2 -64
  17. package/dist/cli/commands/client-admin.js +3 -18
  18. package/dist/cli/commands/mining-runtime.js +4 -60
  19. package/dist/cli/commands/wallet-admin.js +6 -6
  20. package/dist/cli/context.js +1 -3
  21. package/dist/cli/mining-json.d.ts +1 -22
  22. package/dist/cli/mining-json.js +0 -23
  23. package/dist/cli/output.js +16 -2
  24. package/dist/cli/parse.js +0 -2
  25. package/dist/cli/preview-json.d.ts +1 -22
  26. package/dist/cli/preview-json.js +0 -19
  27. package/dist/cli/types.d.ts +1 -3
  28. package/dist/cli/wallet-format.js +1 -1
  29. package/dist/cli/workflow-hints.d.ts +1 -2
  30. package/dist/cli/workflow-hints.js +5 -8
  31. package/dist/wallet/lifecycle/context.js +0 -1
  32. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  33. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  34. package/dist/wallet/lifecycle/repair.js +0 -3
  35. package/dist/wallet/lifecycle/setup.js +10 -8
  36. package/dist/wallet/lifecycle/types.d.ts +1 -4
  37. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  38. package/dist/wallet/managed-core-wallet.js +27 -1
  39. package/dist/wallet/mining/candidate.d.ts +1 -0
  40. package/dist/wallet/mining/candidate.js +38 -6
  41. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  42. package/dist/wallet/mining/competitiveness.js +6 -0
  43. package/dist/wallet/mining/cycle.d.ts +2 -0
  44. package/dist/wallet/mining/cycle.js +14 -4
  45. package/dist/wallet/mining/engine-state.js +10 -0
  46. package/dist/wallet/mining/engine-types.d.ts +1 -0
  47. package/dist/wallet/mining/index.d.ts +1 -1
  48. package/dist/wallet/mining/index.js +1 -1
  49. package/dist/wallet/mining/publish.d.ts +3 -0
  50. package/dist/wallet/mining/publish.js +78 -6
  51. package/dist/wallet/mining/runner.d.ts +0 -32
  52. package/dist/wallet/mining/runner.js +59 -104
  53. package/dist/wallet/mining/stop.d.ts +7 -0
  54. package/dist/wallet/mining/stop.js +23 -0
  55. package/dist/wallet/mining/supervisor.d.ts +2 -36
  56. package/dist/wallet/mining/supervisor.js +139 -246
  57. package/dist/wallet/mining/visualizer-sync.js +79 -15
  58. package/dist/wallet/read/context.d.ts +1 -5
  59. package/dist/wallet/read/context.js +21 -205
  60. package/dist/wallet/read/managed-services.d.ts +33 -0
  61. package/dist/wallet/read/managed-services.js +222 -0
  62. package/dist/wallet/reset/artifacts.d.ts +16 -0
  63. package/dist/wallet/reset/artifacts.js +141 -0
  64. package/dist/wallet/reset/execution.d.ts +38 -0
  65. package/dist/wallet/reset/execution.js +458 -0
  66. package/dist/wallet/reset/preflight.d.ts +7 -0
  67. package/dist/wallet/reset/preflight.js +116 -0
  68. package/dist/wallet/reset/preview.d.ts +2 -0
  69. package/dist/wallet/reset/preview.js +50 -0
  70. package/dist/wallet/reset/process-cleanup.d.ts +12 -0
  71. package/dist/wallet/reset/process-cleanup.js +179 -0
  72. package/dist/wallet/reset/types.d.ts +189 -0
  73. package/dist/wallet/reset/types.js +1 -0
  74. package/dist/wallet/reset.d.ts +4 -119
  75. package/dist/wallet/reset.js +4 -882
  76. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  77. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  78. package/dist/wallet/state/client-password/context.d.ts +10 -0
  79. package/dist/wallet/state/client-password/context.js +46 -0
  80. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  81. package/dist/wallet/state/client-password/crypto.js +117 -0
  82. package/dist/wallet/state/client-password/files.d.ts +10 -0
  83. package/dist/wallet/state/client-password/files.js +109 -0
  84. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  85. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  86. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  87. package/dist/wallet/state/client-password/messages.js +9 -0
  88. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  89. package/dist/wallet/state/client-password/migration.js +32 -0
  90. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  91. package/dist/wallet/state/client-password/prompts.js +79 -0
  92. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  93. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  94. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  95. package/dist/wallet/state/client-password/readiness.js +48 -0
  96. package/dist/wallet/state/client-password/references.d.ts +1 -0
  97. package/dist/wallet/state/client-password/references.js +56 -0
  98. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  99. package/dist/wallet/state/client-password/rotation.js +98 -0
  100. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  101. package/dist/wallet/state/client-password/session-policy.js +28 -0
  102. package/dist/wallet/state/client-password/session.d.ts +19 -0
  103. package/dist/wallet/state/client-password/session.js +170 -0
  104. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  105. package/dist/wallet/state/client-password/setup.js +49 -0
  106. package/dist/wallet/state/client-password/types.d.ts +82 -0
  107. package/dist/wallet/state/client-password/types.js +5 -0
  108. package/dist/wallet/state/client-password.d.ts +7 -38
  109. package/dist/wallet/state/client-password.js +52 -937
  110. package/dist/wallet/tx/anchor.js +123 -216
  111. package/dist/wallet/tx/cog.js +294 -489
  112. package/dist/wallet/tx/common.d.ts +2 -0
  113. package/dist/wallet/tx/common.js +2 -0
  114. package/dist/wallet/tx/domain-admin.js +111 -220
  115. package/dist/wallet/tx/domain-market.js +401 -681
  116. package/dist/wallet/tx/executor.d.ts +176 -0
  117. package/dist/wallet/tx/executor.js +302 -0
  118. package/dist/wallet/tx/field.js +109 -215
  119. package/dist/wallet/tx/register.js +158 -269
  120. package/dist/wallet/tx/reputation.js +120 -227
  121. package/package.json +1 -1
  122. package/dist/wallet/mining/worker-main.d.ts +0 -1
  123. package/dist/wallet/mining/worker-main.js +0 -17
  124. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  125. package/dist/wallet/state/client-password-agent.js +0 -211
@@ -2,15 +2,15 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import { getBalance, getLock, lookupDomain } from "@cogcoin/indexer/queries";
3
3
  import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
4
4
  import { createRpcClient } from "../../bitcoind/node.js";
5
- import { acquireFileLock } from "../fs/lock.js";
6
- import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
7
- import { createDefaultWalletSecretProvider, } from "../state/provider.js";
5
+ import {} from "../runtime.js";
6
+ import {} from "../state/provider.js";
8
7
  import { serializeCogClaim, serializeCogLock, serializeCogTransfer, } from "../cogop/index.js";
9
8
  import { openWalletReadContext } from "../read/index.js";
10
- import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createBuiltWalletMutationFeeSummary, createFundingMutationSender, createWalletMutationFeeMetadata, formatCogAmount, getDecodedInputScriptPubKeyHex, isLocalWalletScript, isAlreadyAcceptedError, isBroadcastUnknownError, mergeFixedWalletInputs, outpointKey, pauseMiningForWalletMutation, resolvePendingMutationReuseDecision, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
9
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createFundingMutationSender, createWalletMutationFeeMetadata, formatCogAmount, getDecodedInputScriptPubKeyHex, isLocalWalletScript, mergeFixedWalletInputs, outpointKey, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
10
  import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
11
+ import { executeWalletMutationOperation, publishWalletMutation, resolveExistingWalletMutation, } from "./executor.js";
12
12
  import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
13
- import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
13
+ import { upsertPendingMutation } from "./journal.js";
14
14
  import { normalizeBtcTarget } from "./targets.js";
15
15
  const MAX_LOCK_DURATION_BLOCKS = 262_800;
16
16
  const ZERO_PREIMAGE_HEX = "00".repeat(32);
@@ -411,278 +411,141 @@ async function confirmClaim(prompter, options) {
411
411
  : "wallet_reclaim_requires_tty",
412
412
  });
413
413
  }
414
- async function sendBuiltTransaction(options) {
415
- let nextState = options.state;
416
- const broadcasting = updateMutationRecord(options.mutation, "broadcasting", options.nowUnixMs, {
417
- attemptedTxid: options.built.txid,
418
- attemptedWtxid: options.built.wtxid,
419
- temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
420
- });
421
- nextState = {
422
- ...upsertPendingMutation(nextState, broadcasting),
423
- stateRevision: nextState.stateRevision + 1,
424
- lastWrittenAtUnixMs: options.nowUnixMs,
425
- };
426
- await saveWalletStatePreservingUnlock({
427
- state: nextState,
428
- provider: options.provider,
429
- nowUnixMs: options.nowUnixMs,
430
- paths: options.paths,
431
- });
432
- if (options.snapshotHeight !== null && options.snapshotHeight !== (await options.rpc.getBlockchainInfo()).blocks) {
433
- await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
434
- throw new Error(`${options.errorPrefix}_tip_mismatch`);
435
- }
436
- try {
437
- await options.rpc.sendRawTransaction(options.built.rawHex);
438
- }
439
- catch (error) {
440
- if (!isAlreadyAcceptedError(error)) {
441
- if (isBroadcastUnknownError(error)) {
442
- const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", options.nowUnixMs, {
443
- attemptedTxid: options.built.txid,
444
- attemptedWtxid: options.built.wtxid,
445
- temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
446
- });
447
- nextState = {
448
- ...upsertPendingMutation(nextState, unknown),
449
- stateRevision: nextState.stateRevision + 1,
450
- lastWrittenAtUnixMs: options.nowUnixMs,
451
- };
452
- await saveWalletStatePreservingUnlock({
453
- state: nextState,
454
- provider: options.provider,
455
- nowUnixMs: options.nowUnixMs,
456
- paths: options.paths,
457
- });
458
- throw new Error(`${options.errorPrefix}_broadcast_unknown`);
459
- }
460
- await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
461
- const canceled = updateMutationRecord(broadcasting, "canceled", options.nowUnixMs, {
462
- attemptedTxid: options.built.txid,
463
- attemptedWtxid: options.built.wtxid,
464
- temporaryBuilderLockedOutpoints: [],
465
- });
466
- nextState = {
467
- ...upsertPendingMutation(nextState, canceled),
468
- stateRevision: nextState.stateRevision + 1,
469
- lastWrittenAtUnixMs: options.nowUnixMs,
470
- };
471
- await saveWalletStatePreservingUnlock({
472
- state: nextState,
473
- provider: options.provider,
474
- nowUnixMs: options.nowUnixMs,
475
- paths: options.paths,
476
- });
477
- throw error;
478
- }
479
- }
480
- await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
481
- const live = updateMutationRecord(broadcasting, "live", options.nowUnixMs, {
482
- attemptedTxid: options.built.txid,
483
- attemptedWtxid: options.built.wtxid,
484
- temporaryBuilderLockedOutpoints: [],
485
- });
486
- nextState = {
487
- ...upsertPendingMutation(nextState, live),
488
- stateRevision: nextState.stateRevision + 1,
489
- lastWrittenAtUnixMs: options.nowUnixMs,
490
- };
491
- await saveWalletStatePreservingUnlock({
492
- state: nextState,
493
- provider: options.provider,
494
- nowUnixMs: options.nowUnixMs,
495
- paths: options.paths,
496
- });
497
- return { state: nextState, mutation: live };
498
- }
499
414
  export async function sendCog(options) {
500
- const provider = options.provider ?? createDefaultWalletSecretProvider();
501
- const nowUnixMs = options.nowUnixMs ?? Date.now();
502
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
503
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
504
- purpose: "wallet-send",
505
- walletRootId: null,
506
- });
507
415
  const amountCogtoshi = normalizePositiveAmount(options.amountCogtoshi, "wallet_send_invalid_amount");
508
416
  const recipient = normalizeBtcTarget(options.target);
509
- try {
510
- const miningPreemption = await pauseMiningForWalletMutation({
511
- paths,
512
- reason: "wallet-send",
513
- });
514
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
515
- dataDir: options.dataDir,
516
- databasePath: options.databasePath,
517
- secretProvider: provider,
518
- walletControlLockHeld: true,
519
- paths,
520
- });
521
- try {
417
+ const execution = await executeWalletMutationOperation({
418
+ ...options,
419
+ controlLockPurpose: "wallet-send",
420
+ preemptionReason: "wallet-send",
421
+ resolveOperation(readContext) {
522
422
  const operation = resolveIdentitySender(readContext, "wallet_send", amountCogtoshi, options.fromIdentity);
523
423
  if (operation.sender.scriptPubKeyHex === recipient.scriptPubKeyHex) {
524
424
  throw new Error("wallet_send_self_transfer");
525
425
  }
526
- const intentFingerprintHex = createIntentFingerprint([
426
+ return {
427
+ ...operation,
428
+ amountCogtoshi,
429
+ recipient,
430
+ };
431
+ },
432
+ createIntentFingerprint(operation) {
433
+ return createIntentFingerprint([
527
434
  "send",
528
435
  operation.state.walletRootId,
529
436
  operation.sender.scriptPubKeyHex,
530
- recipient.scriptPubKeyHex,
531
- amountCogtoshi,
437
+ operation.recipient.scriptPubKeyHex,
438
+ operation.amountCogtoshi,
532
439
  ]);
533
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
534
- dataDir: options.dataDir,
535
- chain: "main",
536
- startHeight: 0,
537
- walletRootId: operation.state.walletRootId,
538
- });
539
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
540
- const walletName = operation.state.managedCoreWallet.walletName;
541
- const feeSelection = await resolveWalletMutationFeeSelection({
542
- rpc,
543
- feeRateSatVb: options.feeRateSatVb ?? null,
544
- });
545
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
546
- let workingState = operation.state;
547
- let replacementFixedInputs = null;
548
- if (existingMutation !== null) {
549
- const reconciled = await reconcilePendingCogMutation({
550
- state: operation.state,
551
- mutation: existingMutation,
552
- provider,
553
- nowUnixMs,
554
- paths,
555
- rpc,
556
- walletName,
557
- context: readContext,
558
- });
559
- workingState = reconciled.state;
560
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
561
- const reuse = await resolvePendingMutationReuseDecision({
562
- rpc,
563
- walletName,
564
- mutation: reconciled.mutation,
565
- nextFeeSelection: feeSelection,
566
- });
567
- if (reuse.reuseExisting) {
568
- return {
569
- kind: "send",
570
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
571
- status: reconciled.resolution,
572
- reusedExisting: true,
573
- amountCogtoshi,
574
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
575
- resolved: operation.resolved,
576
- fees: reuse.fees,
577
- };
578
- }
579
- replacementFixedInputs = reuse.replacementFixedInputs;
580
- }
581
- if (reconciled.resolution === "repair-required") {
582
- throw new Error("wallet_send_repair_required");
583
- }
440
+ },
441
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
442
+ if (existingMutation === null) {
443
+ return { state: operation.state, replacementFixedInputs: null, result: null };
584
444
  }
585
- await confirmSend(options.prompter, operation.resolved, options.target, recipient, amountCogtoshi, options.assumeYes);
586
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
587
- kind: "send",
588
- sender: operation.sender,
589
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
590
- amountCogtoshi,
591
- intentFingerprintHex,
592
- nowUnixMs,
593
- feeSelection,
594
- existing: existingMutation,
595
- }));
596
- nextState = {
597
- ...nextState,
598
- stateRevision: nextState.stateRevision + 1,
599
- lastWrittenAtUnixMs: nowUnixMs,
600
- };
601
- await saveWalletStatePreservingUnlock({
602
- state: nextState,
603
- provider,
604
- nowUnixMs,
605
- paths,
445
+ return resolveExistingWalletMutation({
446
+ existingMutation,
447
+ execution,
448
+ repairRequiredErrorCode: "wallet_send_repair_required",
449
+ reconcileExistingMutation: (mutation) => reconcilePendingCogMutation({
450
+ state: operation.state,
451
+ mutation,
452
+ provider: execution.provider,
453
+ nowUnixMs: execution.nowUnixMs,
454
+ paths: execution.paths,
455
+ rpc: execution.rpc,
456
+ walletName: execution.walletName,
457
+ context: execution.readContext,
458
+ }),
459
+ createReuseResult: ({ mutation, resolution, fees }) => ({
460
+ kind: "send",
461
+ txid: mutation.attemptedTxid ?? "unknown",
462
+ status: resolution,
463
+ reusedExisting: true,
464
+ amountCogtoshi: operation.amountCogtoshi,
465
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
466
+ resolved: operation.resolved,
467
+ fees,
468
+ }),
606
469
  });
470
+ },
471
+ confirm({ operation }) {
472
+ return confirmSend(options.prompter, operation.resolved, options.target, operation.recipient, operation.amountCogtoshi, options.assumeYes);
473
+ },
474
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
475
+ return {
476
+ mutation: createDraftMutation({
477
+ kind: "send",
478
+ sender: operation.sender,
479
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
480
+ amountCogtoshi: operation.amountCogtoshi,
481
+ intentFingerprintHex,
482
+ nowUnixMs: execution.nowUnixMs,
483
+ feeSelection: execution.feeSelection,
484
+ existing: existingMutation,
485
+ }),
486
+ prepared: null,
487
+ };
488
+ },
489
+ async build({ operation, state, execution, replacementFixedInputs }) {
607
490
  const sendPlan = buildPlanForCogOperation({
608
- state: nextState,
609
- allUtxos: await rpc.listUnspent(walletName, 1),
491
+ state,
492
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
610
493
  sender: operation.sender,
611
- opReturnData: serializeCogTransfer(amountCogtoshi, Buffer.from(recipient.scriptPubKeyHex, "hex")).opReturnData,
494
+ opReturnData: serializeCogTransfer(operation.amountCogtoshi, Buffer.from(operation.recipient.scriptPubKeyHex, "hex")).opReturnData,
612
495
  errorPrefix: "wallet_send",
613
496
  });
614
- const built = await buildTransaction({
615
- rpc,
616
- walletName,
617
- state: nextState,
497
+ return buildTransaction({
498
+ rpc: execution.rpc,
499
+ walletName: execution.walletName,
500
+ state,
618
501
  plan: {
619
502
  ...sendPlan,
620
503
  fixedInputs: mergeFixedWalletInputs(sendPlan.fixedInputs, replacementFixedInputs),
621
504
  },
622
- feeRateSatVb: feeSelection.feeRateSatVb,
505
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
623
506
  });
624
- const final = await sendBuiltTransaction({
625
- rpc,
626
- walletName,
627
- snapshotHeight: readContext.snapshot?.tip?.height ?? null,
507
+ },
508
+ publish({ state, execution, built, mutation }) {
509
+ return publishWalletMutation({
510
+ rpc: execution.rpc,
511
+ walletName: execution.walletName,
512
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
628
513
  built,
629
- mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
630
- state: nextState,
631
- provider,
632
- nowUnixMs,
633
- paths,
514
+ mutation,
515
+ state,
516
+ provider: execution.provider,
517
+ nowUnixMs: execution.nowUnixMs,
518
+ paths: execution.paths,
634
519
  errorPrefix: "wallet_send",
635
520
  });
521
+ },
522
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
636
523
  return {
637
524
  kind: "send",
638
- txid: final.mutation.attemptedTxid ?? built.txid,
639
- status: "live",
640
- reusedExisting: false,
641
- amountCogtoshi,
642
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
525
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
526
+ status: status,
527
+ reusedExisting,
528
+ amountCogtoshi: operation.amountCogtoshi,
529
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
643
530
  resolved: operation.resolved,
644
- fees: createBuiltWalletMutationFeeSummary({
645
- selection: feeSelection,
646
- built,
647
- }),
531
+ fees,
648
532
  };
649
- }
650
- finally {
651
- await readContext.close();
652
- await miningPreemption.release();
653
- }
654
- }
655
- finally {
656
- await controlLock.release();
657
- }
533
+ },
534
+ });
535
+ return execution.result;
658
536
  }
659
537
  export async function lockCogToDomain(options) {
660
- const provider = options.provider ?? createDefaultWalletSecretProvider();
661
- const nowUnixMs = options.nowUnixMs ?? Date.now();
662
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
663
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
664
- purpose: "wallet-lock-cog",
665
- walletRootId: null,
666
- });
667
538
  const amountCogtoshi = normalizePositiveAmount(options.amountCogtoshi, "wallet_lock_invalid_amount");
668
539
  const normalizedRecipientDomainName = normalizeDomainName(options.recipientDomainName);
669
540
  const condition = parseHex32(options.conditionHex, "wallet_lock_invalid_condition");
670
541
  if (condition.equals(Buffer.alloc(32))) {
671
542
  throw new Error("wallet_lock_invalid_condition");
672
543
  }
673
- try {
674
- const miningPreemption = await pauseMiningForWalletMutation({
675
- paths,
676
- reason: "wallet-cog-lock",
677
- });
678
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
679
- dataDir: options.dataDir,
680
- databasePath: options.databasePath,
681
- secretProvider: provider,
682
- walletControlLockHeld: true,
683
- paths,
684
- });
685
- try {
544
+ const execution = await executeWalletMutationOperation({
545
+ ...options,
546
+ controlLockPurpose: "wallet-lock-cog",
547
+ preemptionReason: "wallet-cog-lock",
548
+ resolveOperation(readContext) {
686
549
  assertWalletMutationContextReady(readContext, "wallet_lock");
687
550
  const currentHeight = readContext.snapshot.state.history.currentHeight;
688
551
  if (currentHeight === null) {
@@ -702,311 +565,253 @@ export async function lockCogToDomain(options) {
702
565
  if (readContext.snapshot.state.consensus.nextLockId === 0xffff_ffff) {
703
566
  throw new Error("wallet_lock_id_space_exhausted");
704
567
  }
705
- const operation = resolveIdentitySender(readContext, "wallet_lock", amountCogtoshi, options.fromIdentity);
706
- const intentFingerprintHex = createIntentFingerprint([
568
+ return {
569
+ ...resolveIdentitySender(readContext, "wallet_lock", amountCogtoshi, options.fromIdentity),
570
+ amountCogtoshi,
571
+ normalizedRecipientDomainName,
572
+ recipientDomain,
573
+ timeoutHeight,
574
+ conditionHex: Buffer.from(condition).toString("hex"),
575
+ };
576
+ },
577
+ createIntentFingerprint(operation) {
578
+ return createIntentFingerprint([
707
579
  "lock",
708
580
  operation.state.walletRootId,
709
581
  operation.sender.scriptPubKeyHex,
710
- normalizedRecipientDomainName,
711
- amountCogtoshi,
712
- timeoutHeight,
713
- Buffer.from(condition).toString("hex"),
582
+ operation.normalizedRecipientDomainName,
583
+ operation.amountCogtoshi,
584
+ operation.timeoutHeight,
585
+ operation.conditionHex,
714
586
  ]);
715
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
716
- dataDir: options.dataDir,
717
- chain: "main",
718
- startHeight: 0,
719
- walletRootId: operation.state.walletRootId,
720
- });
721
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
722
- const walletName = operation.state.managedCoreWallet.walletName;
723
- const feeSelection = await resolveWalletMutationFeeSelection({
724
- rpc,
725
- feeRateSatVb: options.feeRateSatVb ?? null,
726
- });
727
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
728
- let workingState = operation.state;
729
- let replacementFixedInputs = null;
730
- if (existingMutation !== null) {
731
- const reconciled = await reconcilePendingCogMutation({
732
- state: operation.state,
733
- mutation: existingMutation,
734
- provider,
735
- nowUnixMs,
736
- paths,
737
- rpc,
738
- walletName,
739
- context: readContext,
740
- });
741
- workingState = reconciled.state;
742
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
743
- const reuse = await resolvePendingMutationReuseDecision({
744
- rpc,
745
- walletName,
746
- mutation: reconciled.mutation,
747
- nextFeeSelection: feeSelection,
748
- });
749
- if (reuse.reuseExisting) {
750
- return {
751
- kind: "lock",
752
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
753
- status: reconciled.resolution,
754
- reusedExisting: true,
755
- amountCogtoshi,
756
- recipientDomainName: normalizedRecipientDomainName,
757
- resolved: operation.resolved,
758
- fees: reuse.fees,
759
- };
760
- }
761
- replacementFixedInputs = reuse.replacementFixedInputs;
762
- }
763
- if (reconciled.resolution === "repair-required") {
764
- throw new Error("wallet_lock_repair_required");
765
- }
587
+ },
588
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
589
+ if (existingMutation === null) {
590
+ return { state: operation.state, replacementFixedInputs: null, result: null };
766
591
  }
767
- await confirmLock(options.prompter, operation.resolved, amountCogtoshi, normalizedRecipientDomainName, timeoutHeight, options.assumeYes);
768
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
769
- kind: "lock",
770
- sender: operation.sender,
771
- amountCogtoshi,
772
- recipientDomainName: normalizedRecipientDomainName,
773
- timeoutHeight,
774
- conditionHex: Buffer.from(condition).toString("hex"),
775
- intentFingerprintHex,
776
- nowUnixMs,
777
- feeSelection,
778
- existing: existingMutation,
779
- }));
780
- nextState = {
781
- ...nextState,
782
- stateRevision: nextState.stateRevision + 1,
783
- lastWrittenAtUnixMs: nowUnixMs,
784
- };
785
- await saveWalletStatePreservingUnlock({
786
- state: nextState,
787
- provider,
788
- nowUnixMs,
789
- paths,
592
+ return resolveExistingWalletMutation({
593
+ existingMutation,
594
+ execution,
595
+ repairRequiredErrorCode: "wallet_lock_repair_required",
596
+ reconcileExistingMutation: (mutation) => reconcilePendingCogMutation({
597
+ state: operation.state,
598
+ mutation,
599
+ provider: execution.provider,
600
+ nowUnixMs: execution.nowUnixMs,
601
+ paths: execution.paths,
602
+ rpc: execution.rpc,
603
+ walletName: execution.walletName,
604
+ context: execution.readContext,
605
+ }),
606
+ createReuseResult: ({ mutation, resolution, fees }) => ({
607
+ kind: "lock",
608
+ txid: mutation.attemptedTxid ?? "unknown",
609
+ status: resolution,
610
+ reusedExisting: true,
611
+ amountCogtoshi: operation.amountCogtoshi,
612
+ recipientDomainName: operation.normalizedRecipientDomainName,
613
+ resolved: operation.resolved,
614
+ fees,
615
+ }),
790
616
  });
617
+ },
618
+ confirm({ operation }) {
619
+ return confirmLock(options.prompter, operation.resolved, operation.amountCogtoshi, operation.normalizedRecipientDomainName, operation.timeoutHeight, options.assumeYes);
620
+ },
621
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
622
+ return {
623
+ mutation: createDraftMutation({
624
+ kind: "lock",
625
+ sender: operation.sender,
626
+ amountCogtoshi: operation.amountCogtoshi,
627
+ recipientDomainName: operation.normalizedRecipientDomainName,
628
+ timeoutHeight: operation.timeoutHeight,
629
+ conditionHex: operation.conditionHex,
630
+ intentFingerprintHex,
631
+ nowUnixMs: execution.nowUnixMs,
632
+ feeSelection: execution.feeSelection,
633
+ existing: existingMutation,
634
+ }),
635
+ prepared: null,
636
+ };
637
+ },
638
+ async build({ operation, state, execution, replacementFixedInputs }) {
791
639
  const lockPlan = buildPlanForCogOperation({
792
- state: nextState,
793
- allUtxos: await rpc.listUnspent(walletName, 1),
640
+ state,
641
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
794
642
  sender: operation.sender,
795
- opReturnData: serializeCogLock(amountCogtoshi, timeoutHeight, recipientDomain.domainId, condition).opReturnData,
643
+ opReturnData: serializeCogLock(operation.amountCogtoshi, operation.timeoutHeight, operation.recipientDomain.domainId, Buffer.from(operation.conditionHex, "hex")).opReturnData,
796
644
  errorPrefix: "wallet_lock",
797
645
  });
798
- const built = await buildTransaction({
799
- rpc,
800
- walletName,
801
- state: nextState,
646
+ return buildTransaction({
647
+ rpc: execution.rpc,
648
+ walletName: execution.walletName,
649
+ state,
802
650
  plan: {
803
651
  ...lockPlan,
804
652
  fixedInputs: mergeFixedWalletInputs(lockPlan.fixedInputs, replacementFixedInputs),
805
653
  },
806
- feeRateSatVb: feeSelection.feeRateSatVb,
654
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
807
655
  });
808
- const final = await sendBuiltTransaction({
809
- rpc,
810
- walletName,
811
- snapshotHeight: readContext.snapshot?.tip?.height ?? null,
656
+ },
657
+ publish({ state, execution, built, mutation }) {
658
+ return publishWalletMutation({
659
+ rpc: execution.rpc,
660
+ walletName: execution.walletName,
661
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
812
662
  built,
813
- mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
814
- state: nextState,
815
- provider,
816
- nowUnixMs,
817
- paths,
663
+ mutation,
664
+ state,
665
+ provider: execution.provider,
666
+ nowUnixMs: execution.nowUnixMs,
667
+ paths: execution.paths,
818
668
  errorPrefix: "wallet_lock",
819
669
  });
670
+ },
671
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
820
672
  return {
821
673
  kind: "lock",
822
- txid: final.mutation.attemptedTxid ?? built.txid,
823
- status: "live",
824
- reusedExisting: false,
825
- amountCogtoshi,
826
- recipientDomainName: normalizedRecipientDomainName,
674
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
675
+ status: status,
676
+ reusedExisting,
677
+ amountCogtoshi: operation.amountCogtoshi,
678
+ recipientDomainName: operation.normalizedRecipientDomainName,
827
679
  resolved: operation.resolved,
828
- fees: createBuiltWalletMutationFeeSummary({
829
- selection: feeSelection,
830
- built,
831
- }),
680
+ fees,
832
681
  };
833
- }
834
- finally {
835
- await readContext.close();
836
- await miningPreemption.release();
837
- }
838
- }
839
- finally {
840
- await controlLock.release();
841
- }
682
+ },
683
+ });
684
+ return execution.result;
842
685
  }
843
686
  async function runClaimLikeMutation(options, reclaim) {
844
- const provider = options.provider ?? createDefaultWalletSecretProvider();
845
- const nowUnixMs = options.nowUnixMs ?? Date.now();
846
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
847
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
848
- purpose: reclaim ? "wallet-reclaim" : "wallet-claim",
849
- walletRootId: null,
850
- });
851
687
  const preimageHex = reclaim ? ZERO_PREIMAGE_HEX : options.preimageHex;
852
- try {
853
- const miningPreemption = await pauseMiningForWalletMutation({
854
- paths,
855
- reason: reclaim ? "wallet-reclaim" : "wallet-claim",
856
- });
857
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
858
- dataDir: options.dataDir,
859
- databasePath: options.databasePath,
860
- secretProvider: provider,
861
- walletControlLockHeld: true,
862
- paths,
863
- });
864
- try {
865
- const operation = resolveClaimSender(readContext, options.lockId, preimageHex, reclaim);
866
- const errorPrefix = reclaim ? "wallet_reclaim" : "wallet_claim";
867
- const intentFingerprintHex = createIntentFingerprint([
688
+ const errorPrefix = reclaim ? "wallet_reclaim" : "wallet_claim";
689
+ const execution = await executeWalletMutationOperation({
690
+ ...options,
691
+ controlLockPurpose: reclaim ? "wallet-reclaim" : "wallet-claim",
692
+ preemptionReason: reclaim ? "wallet-reclaim" : "wallet-claim",
693
+ resolveOperation(readContext) {
694
+ return {
695
+ ...resolveClaimSender(readContext, options.lockId, preimageHex, reclaim),
696
+ preimageHex,
697
+ errorPrefix,
698
+ };
699
+ },
700
+ createIntentFingerprint(operation) {
701
+ return createIntentFingerprint([
868
702
  reclaim ? "reclaim" : "claim",
869
703
  operation.state.walletRootId,
870
704
  operation.sender.scriptPubKeyHex,
871
705
  operation.lockId,
872
- preimageHex,
706
+ operation.preimageHex,
873
707
  ]);
874
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
875
- dataDir: options.dataDir,
876
- chain: "main",
877
- startHeight: 0,
878
- walletRootId: operation.state.walletRootId,
879
- });
880
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
881
- const walletName = operation.state.managedCoreWallet.walletName;
882
- const feeSelection = await resolveWalletMutationFeeSelection({
883
- rpc,
884
- feeRateSatVb: options.feeRateSatVb ?? null,
885
- });
886
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
887
- let workingState = operation.state;
888
- let replacementFixedInputs = null;
889
- if (existingMutation !== null) {
890
- const reconciled = await reconcilePendingCogMutation({
891
- state: operation.state,
892
- mutation: existingMutation,
893
- provider,
894
- nowUnixMs,
895
- paths,
896
- rpc,
897
- walletName,
898
- context: readContext,
899
- });
900
- workingState = reconciled.state;
901
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
902
- const reuse = await resolvePendingMutationReuseDecision({
903
- rpc,
904
- walletName,
905
- mutation: reconciled.mutation,
906
- nextFeeSelection: feeSelection,
907
- });
908
- if (reuse.reuseExisting) {
909
- return {
910
- kind: "claim",
911
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
912
- status: reconciled.resolution,
913
- reusedExisting: true,
914
- amountCogtoshi: operation.amountCogtoshi,
915
- recipientDomainName: operation.recipientDomainName,
916
- lockId: options.lockId,
917
- resolved: operation.resolved,
918
- fees: reuse.fees,
919
- };
920
- }
921
- replacementFixedInputs = reuse.replacementFixedInputs;
922
- }
923
- if (reconciled.resolution === "repair-required") {
924
- throw new Error(`${errorPrefix}_repair_required`);
925
- }
708
+ },
709
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
710
+ if (existingMutation === null) {
711
+ return { state: operation.state, replacementFixedInputs: null, result: null };
926
712
  }
927
- await confirmClaim(options.prompter, {
713
+ return resolveExistingWalletMutation({
714
+ existingMutation,
715
+ execution,
716
+ repairRequiredErrorCode: `${errorPrefix}_repair_required`,
717
+ reconcileExistingMutation: (mutation) => reconcilePendingCogMutation({
718
+ state: operation.state,
719
+ mutation,
720
+ provider: execution.provider,
721
+ nowUnixMs: execution.nowUnixMs,
722
+ paths: execution.paths,
723
+ rpc: execution.rpc,
724
+ walletName: execution.walletName,
725
+ context: execution.readContext,
726
+ }),
727
+ createReuseResult: ({ mutation, resolution, fees }) => ({
728
+ kind: "claim",
729
+ txid: mutation.attemptedTxid ?? "unknown",
730
+ status: resolution,
731
+ reusedExisting: true,
732
+ amountCogtoshi: operation.amountCogtoshi,
733
+ recipientDomainName: operation.recipientDomainName,
734
+ lockId: operation.lockId,
735
+ resolved: operation.resolved,
736
+ fees,
737
+ }),
738
+ });
739
+ },
740
+ confirm({ operation }) {
741
+ return confirmClaim(options.prompter, {
928
742
  kind: reclaim ? "reclaim" : "claim",
929
- lockId: options.lockId,
743
+ lockId: operation.lockId,
930
744
  recipientDomainName: operation.recipientDomainName,
931
745
  amountCogtoshi: operation.amountCogtoshi,
932
746
  resolved: operation.resolved,
933
747
  assumeYes: options.assumeYes,
934
748
  });
935
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
936
- kind: "claim",
937
- sender: operation.sender,
938
- amountCogtoshi: operation.amountCogtoshi,
939
- recipientDomainName: operation.recipientDomainName,
940
- lockId: options.lockId,
941
- preimageHex,
942
- intentFingerprintHex,
943
- nowUnixMs,
944
- feeSelection,
945
- existing: existingMutation,
946
- }));
947
- nextState = {
948
- ...nextState,
949
- stateRevision: nextState.stateRevision + 1,
950
- lastWrittenAtUnixMs: nowUnixMs,
749
+ },
750
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
751
+ return {
752
+ mutation: createDraftMutation({
753
+ kind: "claim",
754
+ sender: operation.sender,
755
+ amountCogtoshi: operation.amountCogtoshi,
756
+ recipientDomainName: operation.recipientDomainName,
757
+ lockId: operation.lockId,
758
+ preimageHex: operation.preimageHex,
759
+ intentFingerprintHex,
760
+ nowUnixMs: execution.nowUnixMs,
761
+ feeSelection: execution.feeSelection,
762
+ existing: existingMutation,
763
+ }),
764
+ prepared: null,
951
765
  };
952
- await saveWalletStatePreservingUnlock({
953
- state: nextState,
954
- provider,
955
- nowUnixMs,
956
- paths,
957
- });
766
+ },
767
+ async build({ operation, state, execution, replacementFixedInputs }) {
958
768
  const claimPlan = buildPlanForCogOperation({
959
- state: nextState,
960
- allUtxos: await rpc.listUnspent(walletName, 1),
769
+ state,
770
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
961
771
  sender: operation.sender,
962
- opReturnData: serializeCogClaim(options.lockId, Buffer.from(preimageHex, "hex")).opReturnData,
772
+ opReturnData: serializeCogClaim(operation.lockId, Buffer.from(operation.preimageHex, "hex")).opReturnData,
963
773
  errorPrefix,
964
774
  });
965
- const built = await buildTransaction({
966
- rpc,
967
- walletName,
968
- state: nextState,
775
+ return buildTransaction({
776
+ rpc: execution.rpc,
777
+ walletName: execution.walletName,
778
+ state,
969
779
  plan: {
970
780
  ...claimPlan,
971
781
  fixedInputs: mergeFixedWalletInputs(claimPlan.fixedInputs, replacementFixedInputs),
972
782
  },
973
- feeRateSatVb: feeSelection.feeRateSatVb,
783
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
974
784
  });
975
- const final = await sendBuiltTransaction({
976
- rpc,
977
- walletName,
978
- snapshotHeight: readContext.snapshot?.tip?.height ?? null,
785
+ },
786
+ publish({ state, execution, built, mutation }) {
787
+ return publishWalletMutation({
788
+ rpc: execution.rpc,
789
+ walletName: execution.walletName,
790
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
979
791
  built,
980
- mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
981
- state: nextState,
982
- provider,
983
- nowUnixMs,
984
- paths,
792
+ mutation,
793
+ state,
794
+ provider: execution.provider,
795
+ nowUnixMs: execution.nowUnixMs,
796
+ paths: execution.paths,
985
797
  errorPrefix,
986
798
  });
799
+ },
800
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
987
801
  return {
988
802
  kind: "claim",
989
- txid: final.mutation.attemptedTxid ?? built.txid,
990
- status: "live",
991
- reusedExisting: false,
803
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
804
+ status: status,
805
+ reusedExisting,
992
806
  amountCogtoshi: operation.amountCogtoshi,
993
807
  recipientDomainName: operation.recipientDomainName,
994
- lockId: options.lockId,
808
+ lockId: operation.lockId,
995
809
  resolved: operation.resolved,
996
- fees: createBuiltWalletMutationFeeSummary({
997
- selection: feeSelection,
998
- built,
999
- }),
810
+ fees,
1000
811
  };
1001
- }
1002
- finally {
1003
- await readContext.close();
1004
- await miningPreemption.release();
1005
- }
1006
- }
1007
- finally {
1008
- await controlLock.release();
1009
- }
812
+ },
813
+ });
814
+ return execution.result;
1010
815
  }
1011
816
  export async function claimCogLock(options) {
1012
817
  return runClaimLikeMutation(options, false);