@cogcoin/client 1.1.5 → 1.1.7

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 (132) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +39 -204
  4. package/dist/bitcoind/managed-runtime/bitcoind-policy.d.ts +16 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-policy.js +177 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  7. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  8. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  10. package/dist/bitcoind/managed-runtime/indexer-policy.d.ts +34 -0
  11. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  12. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  13. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  14. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  15. package/dist/bitcoind/managed-runtime/status.js +59 -0
  16. package/dist/bitcoind/managed-runtime/types.d.ts +77 -0
  17. package/dist/bitcoind/node.d.ts +2 -2
  18. package/dist/bitcoind/node.js +2 -2
  19. package/dist/bitcoind/rpc.d.ts +2 -1
  20. package/dist/bitcoind/rpc.js +53 -3
  21. package/dist/bitcoind/service.d.ts +2 -7
  22. package/dist/bitcoind/service.js +79 -207
  23. package/dist/cli/command-registry.d.ts +1 -1
  24. package/dist/cli/command-registry.js +2 -64
  25. package/dist/cli/commands/client-admin.js +3 -18
  26. package/dist/cli/commands/mining-runtime.js +4 -60
  27. package/dist/cli/commands/wallet-admin.js +6 -6
  28. package/dist/cli/context.js +1 -3
  29. package/dist/cli/mining-json.d.ts +1 -22
  30. package/dist/cli/mining-json.js +0 -23
  31. package/dist/cli/output.js +16 -2
  32. package/dist/cli/parse.js +0 -2
  33. package/dist/cli/preview-json.d.ts +1 -22
  34. package/dist/cli/preview-json.js +0 -19
  35. package/dist/cli/types.d.ts +1 -3
  36. package/dist/cli/wallet-format.js +1 -1
  37. package/dist/cli/workflow-hints.d.ts +1 -2
  38. package/dist/cli/workflow-hints.js +5 -8
  39. package/dist/wallet/lifecycle/access.d.ts +5 -0
  40. package/dist/wallet/lifecycle/access.js +79 -0
  41. package/dist/wallet/lifecycle/context.d.ts +26 -0
  42. package/dist/wallet/lifecycle/context.js +57 -0
  43. package/dist/wallet/lifecycle/managed-core.d.ts +1 -9
  44. package/dist/wallet/lifecycle/managed-core.js +3 -63
  45. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  46. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  47. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  48. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  49. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  50. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  51. package/dist/wallet/lifecycle/repair.d.ts +2 -4
  52. package/dist/wallet/lifecycle/repair.js +74 -318
  53. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  54. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  55. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  56. package/dist/wallet/lifecycle/setup-state.js +159 -0
  57. package/dist/wallet/lifecycle/setup.d.ts +3 -4
  58. package/dist/wallet/lifecycle/setup.js +47 -351
  59. package/dist/wallet/lifecycle/types.d.ts +33 -5
  60. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  61. package/dist/wallet/managed-core-wallet.js +27 -1
  62. package/dist/wallet/mining/candidate.d.ts +1 -0
  63. package/dist/wallet/mining/candidate.js +38 -6
  64. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  65. package/dist/wallet/mining/competitiveness.js +6 -0
  66. package/dist/wallet/mining/cycle.d.ts +2 -0
  67. package/dist/wallet/mining/cycle.js +14 -4
  68. package/dist/wallet/mining/engine-types.d.ts +1 -0
  69. package/dist/wallet/mining/index.d.ts +1 -1
  70. package/dist/wallet/mining/index.js +1 -1
  71. package/dist/wallet/mining/publish.d.ts +3 -0
  72. package/dist/wallet/mining/publish.js +78 -6
  73. package/dist/wallet/mining/runner.d.ts +0 -32
  74. package/dist/wallet/mining/runner.js +59 -104
  75. package/dist/wallet/mining/stop.d.ts +7 -0
  76. package/dist/wallet/mining/stop.js +23 -0
  77. package/dist/wallet/mining/supervisor.d.ts +2 -36
  78. package/dist/wallet/mining/supervisor.js +139 -246
  79. package/dist/wallet/read/context.d.ts +1 -5
  80. package/dist/wallet/read/context.js +20 -379
  81. package/dist/wallet/read/managed-services.d.ts +33 -0
  82. package/dist/wallet/read/managed-services.js +222 -0
  83. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  84. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  85. package/dist/wallet/state/client-password/context.d.ts +10 -0
  86. package/dist/wallet/state/client-password/context.js +46 -0
  87. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  88. package/dist/wallet/state/client-password/crypto.js +117 -0
  89. package/dist/wallet/state/client-password/files.d.ts +10 -0
  90. package/dist/wallet/state/client-password/files.js +109 -0
  91. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  92. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  93. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  94. package/dist/wallet/state/client-password/messages.js +9 -0
  95. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  96. package/dist/wallet/state/client-password/migration.js +32 -0
  97. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  98. package/dist/wallet/state/client-password/prompts.js +79 -0
  99. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  100. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  101. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  102. package/dist/wallet/state/client-password/readiness.js +48 -0
  103. package/dist/wallet/state/client-password/references.d.ts +1 -0
  104. package/dist/wallet/state/client-password/references.js +56 -0
  105. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  106. package/dist/wallet/state/client-password/rotation.js +98 -0
  107. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  108. package/dist/wallet/state/client-password/session-policy.js +28 -0
  109. package/dist/wallet/state/client-password/session.d.ts +19 -0
  110. package/dist/wallet/state/client-password/session.js +170 -0
  111. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  112. package/dist/wallet/state/client-password/setup.js +49 -0
  113. package/dist/wallet/state/client-password/types.d.ts +82 -0
  114. package/dist/wallet/state/client-password/types.js +5 -0
  115. package/dist/wallet/state/client-password.d.ts +7 -38
  116. package/dist/wallet/state/client-password.js +52 -937
  117. package/dist/wallet/tx/anchor.js +123 -216
  118. package/dist/wallet/tx/cog.js +294 -489
  119. package/dist/wallet/tx/common.d.ts +2 -0
  120. package/dist/wallet/tx/common.js +2 -0
  121. package/dist/wallet/tx/domain-admin.js +111 -220
  122. package/dist/wallet/tx/domain-market.js +401 -681
  123. package/dist/wallet/tx/executor.d.ts +176 -0
  124. package/dist/wallet/tx/executor.js +302 -0
  125. package/dist/wallet/tx/field.js +109 -215
  126. package/dist/wallet/tx/register.js +158 -269
  127. package/dist/wallet/tx/reputation.js +120 -227
  128. package/package.json +1 -1
  129. package/dist/wallet/mining/worker-main.js +0 -17
  130. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  131. package/dist/wallet/state/client-password-agent.js +0 -211
  132. /package/dist/{wallet/mining/worker-main.d.ts → bitcoind/managed-runtime/types.js} +0 -0
@@ -2,16 +2,16 @@ import { createHash, randomBytes } from "node:crypto";
2
2
  import { getBalance, getListing, 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";
5
+ import {} from "../runtime.js";
7
6
  import { reconcilePersistentPolicyLocks as reconcileWalletCoinControlLocks } from "../coin-control.js";
8
- import { createDefaultWalletSecretProvider, } from "../state/provider.js";
7
+ import {} from "../state/provider.js";
9
8
  import { serializeDomainBuy, serializeDomainSell, serializeDomainTransfer, validateDomainName, } from "../cogop/index.js";
10
9
  import { openWalletReadContext } from "../read/index.js";
11
- import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createBuiltWalletMutationFeeSummary, createFundingMutationSender, createWalletMutationFeeMetadata, getDecodedInputScriptPubKeyHex, isLocalWalletScript, isAlreadyAcceptedError, isBroadcastUnknownError, mergeFixedWalletInputs, outpointKey, pauseMiningForWalletMutation, resolvePendingMutationReuseDecision, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
10
+ import { assertFixedInputPrefixMatches, assertFundingInputsAfterFixedPrefix, assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createFundingMutationSender, createWalletMutationFeeMetadata, getDecodedInputScriptPubKeyHex, isLocalWalletScript, mergeFixedWalletInputs, outpointKey, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
11
  import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
12
+ import { executeWalletMutationOperation, publishWalletMutation, resolveExistingWalletMutation, } from "./executor.js";
13
13
  import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
14
- import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
14
+ import { upsertPendingMutation } from "./journal.js";
15
15
  import { normalizeBtcTarget } from "./targets.js";
16
16
  async function prepareDomainMarketBuildState(options) {
17
17
  if (!options.preflightCoinControl) {
@@ -567,520 +567,333 @@ async function confirmBuy(prompter, domainName, buyerSelector, buyer, sellerScri
567
567
  });
568
568
  }
569
569
  export async function transferDomain(options) {
570
- const provider = options.provider ?? createDefaultWalletSecretProvider();
571
- const nowUnixMs = options.nowUnixMs ?? Date.now();
572
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
573
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
574
- purpose: "wallet-transfer",
575
- walletRootId: null,
576
- });
577
570
  const normalizedDomainName = normalizeDomainName(options.domainName);
578
571
  const recipient = normalizeBtcTarget(options.target);
579
- try {
580
- const miningPreemption = await pauseMiningForWalletMutation({
581
- paths,
582
- reason: "wallet-transfer",
583
- });
584
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
585
- dataDir: options.dataDir,
586
- databasePath: options.databasePath,
587
- secretProvider: provider,
588
- walletControlLockHeld: true,
589
- paths,
590
- });
591
- try {
572
+ const execution = await executeWalletMutationOperation({
573
+ ...options,
574
+ controlLockPurpose: "wallet-transfer",
575
+ preemptionReason: "wallet-transfer",
576
+ resolveOperation(readContext) {
592
577
  const operation = resolveOwnedDomainOperation(readContext, normalizedDomainName, "wallet_transfer");
593
- const snapshot = readContext.snapshot;
594
- const model = readContext.model;
595
578
  const resolvedSender = createResolvedDomainMarketSenderSummary(operation.sender, operation.senderSelector);
596
579
  const resolvedRecipient = createResolvedDomainMarketRecipientSummary(recipient);
597
- const resolvedEconomicEffect = createTransferEconomicEffectSummary(getListing(snapshot.state, operation.chainDomain.domainId) !== null);
580
+ const resolvedEconomicEffect = createTransferEconomicEffectSummary(getListing(readContext.snapshot.state, operation.chainDomain.domainId) !== null);
598
581
  if (operation.sender.scriptPubKeyHex === recipient.scriptPubKeyHex) {
599
582
  throw new Error("wallet_transfer_self_transfer");
600
583
  }
601
- const intentFingerprintHex = createIntentFingerprint([
584
+ return {
585
+ ...operation,
586
+ normalizedDomainName,
587
+ recipient,
588
+ resolvedSender,
589
+ resolvedRecipient,
590
+ resolvedEconomicEffect,
591
+ };
592
+ },
593
+ createIntentFingerprint(operation) {
594
+ return createIntentFingerprint([
602
595
  "transfer",
603
596
  operation.state.walletRootId,
604
- normalizedDomainName,
597
+ operation.normalizedDomainName,
605
598
  operation.sender.scriptPubKeyHex,
606
- recipient.scriptPubKeyHex,
599
+ operation.recipient.scriptPubKeyHex,
607
600
  ]);
608
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
609
- dataDir: options.dataDir,
610
- chain: "main",
611
- startHeight: 0,
612
- walletRootId: operation.state.walletRootId,
613
- });
614
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
615
- const walletName = operation.state.managedCoreWallet.walletName;
616
- const feeSelection = await resolveWalletMutationFeeSelection({
617
- rpc,
618
- feeRateSatVb: options.feeRateSatVb ?? null,
619
- });
620
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
621
- let workingState = operation.state;
622
- let replacementFixedInputs = null;
623
- if (existingMutation !== null) {
624
- const reconciled = await reconcilePendingMutation({
625
- state: operation.state,
626
- mutation: existingMutation,
627
- provider,
628
- nowUnixMs,
629
- paths,
630
- rpc,
631
- walletName,
632
- context: readContext,
633
- });
634
- workingState = reconciled.state;
635
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
636
- const reuse = await resolvePendingMutationReuseDecision({
637
- rpc,
638
- walletName,
639
- mutation: reconciled.mutation,
640
- nextFeeSelection: feeSelection,
641
- });
642
- if (reuse.reuseExisting) {
643
- return {
644
- kind: "transfer",
645
- domainName: normalizedDomainName,
646
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
647
- status: reconciled.resolution,
648
- reusedExisting: true,
649
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
650
- resolved: {
651
- sender: resolvedSender,
652
- recipient: resolvedRecipient,
653
- economicEffect: resolvedEconomicEffect,
654
- },
655
- fees: reuse.fees,
656
- };
657
- }
658
- replacementFixedInputs = reuse.replacementFixedInputs;
659
- }
660
- if (reconciled.resolution === "repair-required") {
661
- throw new Error("wallet_transfer_repair_required");
662
- }
601
+ },
602
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
603
+ if (existingMutation === null) {
604
+ return { state: operation.state, replacementFixedInputs: null, result: null };
663
605
  }
664
- await confirmTransfer(options.prompter, normalizedDomainName, resolvedSender, resolvedRecipient, resolvedEconomicEffect, options.assumeYes);
665
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
666
- kind: "transfer",
667
- domainName: normalizedDomainName,
668
- sender: operation.sender,
669
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
670
- intentFingerprintHex,
671
- nowUnixMs,
672
- feeSelection,
673
- existing: existingMutation,
674
- }));
675
- nextState = {
676
- ...nextState,
677
- stateRevision: nextState.stateRevision + 1,
678
- lastWrittenAtUnixMs: nowUnixMs,
679
- };
680
- await saveWalletStatePreservingUnlock({
681
- state: nextState,
682
- provider,
683
- nowUnixMs,
684
- paths,
606
+ return resolveExistingWalletMutation({
607
+ existingMutation,
608
+ execution,
609
+ repairRequiredErrorCode: "wallet_transfer_repair_required",
610
+ reconcileExistingMutation: (mutation) => reconcilePendingMutation({
611
+ state: operation.state,
612
+ mutation,
613
+ provider: execution.provider,
614
+ nowUnixMs: execution.nowUnixMs,
615
+ paths: execution.paths,
616
+ rpc: execution.rpc,
617
+ walletName: execution.walletName,
618
+ context: execution.readContext,
619
+ }),
620
+ createReuseResult: ({ mutation, resolution, fees }) => ({
621
+ kind: "transfer",
622
+ domainName: operation.normalizedDomainName,
623
+ txid: mutation.attemptedTxid ?? "unknown",
624
+ status: resolution,
625
+ reusedExisting: true,
626
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
627
+ resolved: {
628
+ sender: operation.resolvedSender,
629
+ recipient: operation.resolvedRecipient,
630
+ economicEffect: operation.resolvedEconomicEffect,
631
+ },
632
+ fees,
633
+ }),
685
634
  });
686
- const buildPreparation = await prepareDomainMarketBuildState({
687
- rpc,
688
- walletName,
689
- state: nextState,
690
- provider,
691
- nowUnixMs,
692
- paths,
635
+ },
636
+ confirm({ operation }) {
637
+ return confirmTransfer(options.prompter, operation.normalizedDomainName, operation.resolvedSender, operation.resolvedRecipient, operation.resolvedEconomicEffect, options.assumeYes);
638
+ },
639
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
640
+ return {
641
+ mutation: createDraftMutation({
642
+ kind: "transfer",
643
+ domainName: operation.normalizedDomainName,
644
+ sender: operation.sender,
645
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
646
+ intentFingerprintHex,
647
+ nowUnixMs: execution.nowUnixMs,
648
+ feeSelection: execution.feeSelection,
649
+ existing: existingMutation,
650
+ }),
651
+ prepared: null,
652
+ };
653
+ },
654
+ async prepareBuildState({ state, execution }) {
655
+ return (await prepareDomainMarketBuildState({
656
+ rpc: execution.rpc,
657
+ walletName: execution.walletName,
658
+ state,
659
+ provider: execution.provider,
660
+ nowUnixMs: execution.nowUnixMs,
661
+ paths: execution.paths,
693
662
  preflightCoinControl: false,
694
- });
695
- nextState = buildPreparation.state;
663
+ })).state;
664
+ },
665
+ async build({ operation, state, execution, replacementFixedInputs }) {
696
666
  const transferPlan = buildPlanForDomainOperation({
697
- state: nextState,
698
- allUtxos: buildPreparation.allUtxos,
667
+ state,
668
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
699
669
  sender: operation.sender,
700
- opReturnData: serializeDomainTransfer(operation.chainDomain.domainId, Buffer.from(recipient.scriptPubKeyHex, "hex")).opReturnData,
670
+ opReturnData: serializeDomainTransfer(operation.chainDomain.domainId, Buffer.from(operation.recipient.scriptPubKeyHex, "hex")).opReturnData,
701
671
  errorPrefix: "wallet_transfer",
702
672
  });
703
- const built = await buildTransaction({
704
- rpc,
705
- walletName,
706
- state: nextState,
673
+ return buildTransaction({
674
+ rpc: execution.rpc,
675
+ walletName: execution.walletName,
676
+ state,
707
677
  plan: {
708
678
  ...transferPlan,
709
679
  fixedInputs: mergeFixedWalletInputs(transferPlan.fixedInputs, replacementFixedInputs),
710
680
  },
711
- feeRateSatVb: feeSelection.feeRateSatVb,
712
- });
713
- const broadcasting = updateMutationRecord(nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex), "broadcasting", nowUnixMs, {
714
- attemptedTxid: built.txid,
715
- attemptedWtxid: built.wtxid,
716
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
717
- });
718
- nextState = {
719
- ...upsertPendingMutation(nextState, broadcasting),
720
- stateRevision: nextState.stateRevision + 1,
721
- lastWrittenAtUnixMs: nowUnixMs,
722
- };
723
- await saveWalletStatePreservingUnlock({
724
- state: nextState,
725
- provider,
726
- nowUnixMs,
727
- paths,
681
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
728
682
  });
729
- if (snapshot.tip?.height !== (await rpc.getBlockchainInfo()).blocks) {
730
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
731
- throw new Error("wallet_transfer_tip_mismatch");
732
- }
733
- try {
734
- await rpc.sendRawTransaction(built.rawHex);
735
- }
736
- catch (error) {
737
- if (!isAlreadyAcceptedError(error)) {
738
- if (isBroadcastUnknownError(error)) {
739
- const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", nowUnixMs, {
740
- attemptedTxid: built.txid,
741
- attemptedWtxid: built.wtxid,
742
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
743
- });
744
- nextState = {
745
- ...upsertPendingMutation(nextState, unknown),
746
- stateRevision: nextState.stateRevision + 1,
747
- lastWrittenAtUnixMs: nowUnixMs,
748
- };
749
- await saveWalletStatePreservingUnlock({
750
- state: nextState,
751
- provider,
752
- nowUnixMs,
753
- paths,
754
- });
755
- throw new Error("wallet_transfer_broadcast_unknown");
756
- }
757
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
758
- const canceled = updateMutationRecord(broadcasting, "canceled", nowUnixMs, {
683
+ },
684
+ publish({ operation, state, execution, built, mutation }) {
685
+ return publishWalletMutation({
686
+ rpc: execution.rpc,
687
+ walletName: execution.walletName,
688
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
689
+ built,
690
+ mutation,
691
+ state,
692
+ provider: execution.provider,
693
+ nowUnixMs: execution.nowUnixMs,
694
+ paths: execution.paths,
695
+ errorPrefix: "wallet_transfer",
696
+ async afterAccepted({ state: acceptedState, broadcastingMutation, built, nowUnixMs }) {
697
+ const finalStatus = getTransferStatusAfterAcceptance({
698
+ snapshot: execution.readContext.snapshot,
699
+ domainName: operation.normalizedDomainName,
700
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
701
+ });
702
+ const finalMutation = updateMutationRecord(broadcastingMutation, finalStatus, nowUnixMs, {
759
703
  attemptedTxid: built.txid,
760
704
  attemptedWtxid: built.wtxid,
761
705
  temporaryBuilderLockedOutpoints: [],
762
706
  });
763
- nextState = {
764
- ...upsertPendingMutation(nextState, canceled),
765
- stateRevision: nextState.stateRevision + 1,
766
- lastWrittenAtUnixMs: nowUnixMs,
707
+ return {
708
+ state: reserveTransferredDomainRecord({
709
+ state: upsertPendingMutation(acceptedState, finalMutation),
710
+ domainName: operation.normalizedDomainName,
711
+ domainId: operation.chainDomain.domainId,
712
+ currentOwnerScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
713
+ nowUnixMs,
714
+ }),
715
+ mutation: finalMutation,
716
+ status: finalStatus,
767
717
  };
768
- await saveWalletStatePreservingUnlock({
769
- state: nextState,
770
- provider,
771
- nowUnixMs,
772
- paths,
773
- });
774
- throw error;
775
- }
776
- }
777
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
778
- const finalStatus = getTransferStatusAfterAcceptance({
779
- snapshot: readContext.snapshot,
780
- domainName: normalizedDomainName,
781
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
782
- });
783
- const finalMutation = updateMutationRecord(broadcasting, finalStatus, nowUnixMs, {
784
- attemptedTxid: built.txid,
785
- attemptedWtxid: built.wtxid,
786
- temporaryBuilderLockedOutpoints: [],
787
- });
788
- nextState = reserveTransferredDomainRecord({
789
- state: upsertPendingMutation(nextState, finalMutation),
790
- domainName: normalizedDomainName,
791
- domainId: operation.chainDomain.domainId,
792
- currentOwnerScriptPubKeyHex: recipient.scriptPubKeyHex,
793
- nowUnixMs,
794
- });
795
- nextState = {
796
- ...nextState,
797
- stateRevision: nextState.stateRevision + 1,
798
- lastWrittenAtUnixMs: nowUnixMs,
799
- };
800
- await saveWalletStatePreservingUnlock({
801
- state: nextState,
802
- provider,
803
- nowUnixMs,
804
- paths,
718
+ },
805
719
  });
720
+ },
721
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
806
722
  return {
807
723
  kind: "transfer",
808
- domainName: normalizedDomainName,
809
- txid: built.txid,
810
- status: finalStatus,
811
- reusedExisting: false,
812
- recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
724
+ domainName: operation.normalizedDomainName,
725
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
726
+ status: status,
727
+ reusedExisting,
728
+ recipientScriptPubKeyHex: operation.recipient.scriptPubKeyHex,
813
729
  resolved: {
814
- sender: resolvedSender,
815
- recipient: resolvedRecipient,
816
- economicEffect: resolvedEconomicEffect,
730
+ sender: operation.resolvedSender,
731
+ recipient: operation.resolvedRecipient,
732
+ economicEffect: operation.resolvedEconomicEffect,
817
733
  },
818
- fees: createBuiltWalletMutationFeeSummary({
819
- selection: feeSelection,
820
- built,
821
- }),
734
+ fees,
822
735
  };
823
- }
824
- finally {
825
- await readContext.close();
826
- await miningPreemption.release();
827
- }
828
- }
829
- finally {
830
- await controlLock.release();
831
- }
736
+ },
737
+ });
738
+ return execution.result;
832
739
  }
833
740
  async function runSellMutation(options) {
834
- const provider = options.provider ?? createDefaultWalletSecretProvider();
835
- const nowUnixMs = options.nowUnixMs ?? Date.now();
836
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
837
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
838
- purpose: "wallet-sell",
839
- walletRootId: null,
840
- });
841
741
  const normalizedDomainName = normalizeDomainName(options.domainName);
842
- try {
843
- const miningPreemption = await pauseMiningForWalletMutation({
844
- paths,
845
- reason: "wallet-sell",
846
- });
847
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
848
- dataDir: options.dataDir,
849
- databasePath: options.databasePath,
850
- secretProvider: provider,
851
- walletControlLockHeld: true,
852
- paths,
853
- });
854
- try {
742
+ const execution = await executeWalletMutationOperation({
743
+ ...options,
744
+ controlLockPurpose: "wallet-sell",
745
+ preemptionReason: "wallet-sell",
746
+ resolveOperation(readContext) {
855
747
  const operation = resolveOwnedDomainOperation(readContext, normalizedDomainName, "wallet_sell");
856
- const resolvedSender = createResolvedDomainMarketSenderSummary(operation.sender, operation.senderSelector);
857
- const resolvedEconomicEffect = createSellEconomicEffectSummary(options.listedPriceCogtoshi);
858
- const snapshot = readContext.snapshot;
859
- const intentFingerprintHex = createIntentFingerprint([
748
+ return {
749
+ ...operation,
750
+ normalizedDomainName,
751
+ listedPriceCogtoshi: options.listedPriceCogtoshi,
752
+ resolvedSender: createResolvedDomainMarketSenderSummary(operation.sender, operation.senderSelector),
753
+ resolvedEconomicEffect: createSellEconomicEffectSummary(options.listedPriceCogtoshi),
754
+ };
755
+ },
756
+ createIntentFingerprint(operation) {
757
+ return createIntentFingerprint([
860
758
  "sell",
861
759
  operation.state.walletRootId,
862
- normalizedDomainName,
760
+ operation.normalizedDomainName,
863
761
  operation.sender.scriptPubKeyHex,
864
- options.listedPriceCogtoshi.toString(),
762
+ operation.listedPriceCogtoshi.toString(),
865
763
  ]);
866
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
867
- dataDir: options.dataDir,
868
- chain: "main",
869
- startHeight: 0,
870
- walletRootId: operation.state.walletRootId,
871
- });
872
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
873
- const walletName = operation.state.managedCoreWallet.walletName;
874
- const feeSelection = await resolveWalletMutationFeeSelection({
875
- rpc,
876
- feeRateSatVb: options.feeRateSatVb ?? null,
877
- });
878
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
879
- let workingState = operation.state;
880
- let replacementFixedInputs = null;
881
- if (existingMutation !== null) {
882
- const reconciled = await reconcilePendingMutation({
883
- state: operation.state,
884
- mutation: existingMutation,
885
- provider,
886
- nowUnixMs,
887
- paths,
888
- rpc,
889
- walletName,
890
- context: readContext,
891
- });
892
- workingState = reconciled.state;
893
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
894
- const reuse = await resolvePendingMutationReuseDecision({
895
- rpc,
896
- walletName,
897
- mutation: reconciled.mutation,
898
- nextFeeSelection: feeSelection,
899
- });
900
- if (reuse.reuseExisting) {
901
- return {
902
- kind: "sell",
903
- domainName: normalizedDomainName,
904
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
905
- status: reconciled.resolution,
906
- reusedExisting: true,
907
- listedPriceCogtoshi: options.listedPriceCogtoshi,
908
- resolved: {
909
- sender: resolvedSender,
910
- economicEffect: resolvedEconomicEffect,
911
- },
912
- fees: reuse.fees,
913
- };
914
- }
915
- replacementFixedInputs = reuse.replacementFixedInputs;
916
- }
917
- if (reconciled.resolution === "repair-required") {
918
- throw new Error("wallet_sell_repair_required");
919
- }
764
+ },
765
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
766
+ if (existingMutation === null) {
767
+ return { state: operation.state, replacementFixedInputs: null, result: null };
920
768
  }
921
- if (options.listedPriceCogtoshi > 0n) {
922
- await confirmSell(options.prompter, normalizedDomainName, resolvedSender, options.listedPriceCogtoshi, options.assumeYes);
769
+ return resolveExistingWalletMutation({
770
+ existingMutation,
771
+ execution,
772
+ repairRequiredErrorCode: "wallet_sell_repair_required",
773
+ reconcileExistingMutation: (mutation) => reconcilePendingMutation({
774
+ state: operation.state,
775
+ mutation,
776
+ provider: execution.provider,
777
+ nowUnixMs: execution.nowUnixMs,
778
+ paths: execution.paths,
779
+ rpc: execution.rpc,
780
+ walletName: execution.walletName,
781
+ context: execution.readContext,
782
+ }),
783
+ createReuseResult: ({ mutation, resolution, fees }) => ({
784
+ kind: "sell",
785
+ domainName: operation.normalizedDomainName,
786
+ txid: mutation.attemptedTxid ?? "unknown",
787
+ status: resolution,
788
+ reusedExisting: true,
789
+ listedPriceCogtoshi: operation.listedPriceCogtoshi,
790
+ resolved: {
791
+ sender: operation.resolvedSender,
792
+ economicEffect: operation.resolvedEconomicEffect,
793
+ },
794
+ fees,
795
+ }),
796
+ });
797
+ },
798
+ async confirm({ operation }) {
799
+ if (operation.listedPriceCogtoshi > 0n) {
800
+ await confirmSell(options.prompter, operation.normalizedDomainName, operation.resolvedSender, operation.listedPriceCogtoshi, options.assumeYes);
923
801
  }
924
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
925
- kind: "sell",
926
- domainName: normalizedDomainName,
927
- sender: operation.sender,
928
- priceCogtoshi: options.listedPriceCogtoshi,
929
- intentFingerprintHex,
930
- nowUnixMs,
931
- feeSelection,
932
- existing: existingMutation,
933
- }));
934
- nextState = {
935
- ...nextState,
936
- stateRevision: nextState.stateRevision + 1,
937
- lastWrittenAtUnixMs: nowUnixMs,
802
+ },
803
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
804
+ return {
805
+ mutation: createDraftMutation({
806
+ kind: "sell",
807
+ domainName: operation.normalizedDomainName,
808
+ sender: operation.sender,
809
+ priceCogtoshi: operation.listedPriceCogtoshi,
810
+ intentFingerprintHex,
811
+ nowUnixMs: execution.nowUnixMs,
812
+ feeSelection: execution.feeSelection,
813
+ existing: existingMutation,
814
+ }),
815
+ prepared: null,
938
816
  };
939
- await saveWalletStatePreservingUnlock({
940
- state: nextState,
941
- provider,
942
- nowUnixMs,
943
- paths,
944
- });
945
- const buildPreparation = await prepareDomainMarketBuildState({
946
- rpc,
947
- walletName,
948
- state: nextState,
949
- provider,
950
- nowUnixMs,
951
- paths,
817
+ },
818
+ async prepareBuildState({ state, execution }) {
819
+ return (await prepareDomainMarketBuildState({
820
+ rpc: execution.rpc,
821
+ walletName: execution.walletName,
822
+ state,
823
+ provider: execution.provider,
824
+ nowUnixMs: execution.nowUnixMs,
825
+ paths: execution.paths,
952
826
  preflightCoinControl: false,
953
- });
954
- nextState = buildPreparation.state;
827
+ })).state;
828
+ },
829
+ async build({ operation, state, execution, replacementFixedInputs }) {
955
830
  const sellPlan = buildPlanForDomainOperation({
956
- state: nextState,
957
- allUtxos: buildPreparation.allUtxos,
831
+ state,
832
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
958
833
  sender: operation.sender,
959
- opReturnData: serializeDomainSell(operation.chainDomain.domainId, options.listedPriceCogtoshi).opReturnData,
834
+ opReturnData: serializeDomainSell(operation.chainDomain.domainId, operation.listedPriceCogtoshi).opReturnData,
960
835
  errorPrefix: "wallet_sell",
961
836
  });
962
- const built = await buildTransaction({
963
- rpc,
964
- walletName,
965
- state: nextState,
837
+ return buildTransaction({
838
+ rpc: execution.rpc,
839
+ walletName: execution.walletName,
840
+ state,
966
841
  plan: {
967
842
  ...sellPlan,
968
843
  fixedInputs: mergeFixedWalletInputs(sellPlan.fixedInputs, replacementFixedInputs),
969
844
  },
970
- feeRateSatVb: feeSelection.feeRateSatVb,
845
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
971
846
  });
972
- const broadcasting = updateMutationRecord(nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex), "broadcasting", nowUnixMs, {
973
- attemptedTxid: built.txid,
974
- attemptedWtxid: built.wtxid,
975
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
976
- });
977
- nextState = {
978
- ...upsertPendingMutation(nextState, broadcasting),
979
- stateRevision: nextState.stateRevision + 1,
980
- lastWrittenAtUnixMs: nowUnixMs,
981
- };
982
- await saveWalletStatePreservingUnlock({
983
- state: nextState,
984
- provider,
985
- nowUnixMs,
986
- paths,
987
- });
988
- if (snapshot.tip?.height !== (await rpc.getBlockchainInfo()).blocks) {
989
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
990
- throw new Error("wallet_sell_tip_mismatch");
991
- }
992
- try {
993
- await rpc.sendRawTransaction(built.rawHex);
994
- }
995
- catch (error) {
996
- if (!isAlreadyAcceptedError(error)) {
997
- if (isBroadcastUnknownError(error)) {
998
- const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", nowUnixMs, {
999
- attemptedTxid: built.txid,
1000
- attemptedWtxid: built.wtxid,
1001
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
1002
- });
1003
- nextState = {
1004
- ...upsertPendingMutation(nextState, unknown),
1005
- stateRevision: nextState.stateRevision + 1,
1006
- lastWrittenAtUnixMs: nowUnixMs,
1007
- };
1008
- await saveWalletStatePreservingUnlock({
1009
- state: nextState,
1010
- provider,
1011
- nowUnixMs,
1012
- paths,
1013
- });
1014
- throw new Error("wallet_sell_broadcast_unknown");
1015
- }
1016
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1017
- const canceled = updateMutationRecord(broadcasting, "canceled", nowUnixMs, {
847
+ },
848
+ publish({ operation, state, execution, built, mutation }) {
849
+ return publishWalletMutation({
850
+ rpc: execution.rpc,
851
+ walletName: execution.walletName,
852
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
853
+ built,
854
+ mutation,
855
+ state,
856
+ provider: execution.provider,
857
+ nowUnixMs: execution.nowUnixMs,
858
+ paths: execution.paths,
859
+ errorPrefix: "wallet_sell",
860
+ async afterAccepted({ state: acceptedState, broadcastingMutation, built, nowUnixMs }) {
861
+ const finalStatus = getSellStatusAfterAcceptance({
862
+ snapshot: execution.readContext.snapshot,
863
+ domainName: operation.normalizedDomainName,
864
+ senderScriptPubKeyHex: operation.sender.scriptPubKeyHex,
865
+ listedPriceCogtoshi: operation.listedPriceCogtoshi,
866
+ });
867
+ const finalMutation = updateMutationRecord(broadcastingMutation, finalStatus, nowUnixMs, {
1018
868
  attemptedTxid: built.txid,
1019
869
  attemptedWtxid: built.wtxid,
1020
870
  temporaryBuilderLockedOutpoints: [],
1021
871
  });
1022
- nextState = {
1023
- ...upsertPendingMutation(nextState, canceled),
1024
- stateRevision: nextState.stateRevision + 1,
1025
- lastWrittenAtUnixMs: nowUnixMs,
872
+ return {
873
+ state: upsertPendingMutation(acceptedState, finalMutation),
874
+ mutation: finalMutation,
875
+ status: finalStatus,
1026
876
  };
1027
- await saveWalletStatePreservingUnlock({
1028
- state: nextState,
1029
- provider,
1030
- nowUnixMs,
1031
- paths,
1032
- });
1033
- throw error;
1034
- }
1035
- }
1036
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1037
- const finalStatus = getSellStatusAfterAcceptance({
1038
- snapshot: readContext.snapshot,
1039
- domainName: normalizedDomainName,
1040
- senderScriptPubKeyHex: operation.sender.scriptPubKeyHex,
1041
- listedPriceCogtoshi: options.listedPriceCogtoshi,
1042
- });
1043
- const finalMutation = updateMutationRecord(broadcasting, finalStatus, nowUnixMs, {
1044
- attemptedTxid: built.txid,
1045
- attemptedWtxid: built.wtxid,
1046
- temporaryBuilderLockedOutpoints: [],
1047
- });
1048
- nextState = {
1049
- ...upsertPendingMutation(nextState, finalMutation),
1050
- stateRevision: nextState.stateRevision + 1,
1051
- lastWrittenAtUnixMs: nowUnixMs,
1052
- };
1053
- await saveWalletStatePreservingUnlock({
1054
- state: nextState,
1055
- provider,
1056
- nowUnixMs,
1057
- paths,
877
+ },
1058
878
  });
879
+ },
880
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
1059
881
  return {
1060
882
  kind: "sell",
1061
- domainName: normalizedDomainName,
1062
- txid: built.txid,
1063
- status: finalStatus,
1064
- reusedExisting: false,
1065
- listedPriceCogtoshi: options.listedPriceCogtoshi,
883
+ domainName: operation.normalizedDomainName,
884
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
885
+ status: status,
886
+ reusedExisting,
887
+ listedPriceCogtoshi: operation.listedPriceCogtoshi,
1066
888
  resolved: {
1067
- sender: resolvedSender,
1068
- economicEffect: resolvedEconomicEffect,
889
+ sender: operation.resolvedSender,
890
+ economicEffect: operation.resolvedEconomicEffect,
1069
891
  },
1070
- fees: createBuiltWalletMutationFeeSummary({
1071
- selection: feeSelection,
1072
- built,
1073
- }),
892
+ fees,
1074
893
  };
1075
- }
1076
- finally {
1077
- await readContext.close();
1078
- await miningPreemption.release();
1079
- }
1080
- }
1081
- finally {
1082
- await controlLock.release();
1083
- }
894
+ },
895
+ });
896
+ return execution.result;
1084
897
  }
1085
898
  export async function sellDomain(options) {
1086
899
  if (options.listedPriceCogtoshi < 0n) {
@@ -1089,270 +902,177 @@ export async function sellDomain(options) {
1089
902
  return runSellMutation(options);
1090
903
  }
1091
904
  export async function buyDomain(options) {
1092
- const provider = options.provider ?? createDefaultWalletSecretProvider();
1093
- const nowUnixMs = options.nowUnixMs ?? Date.now();
1094
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1095
- const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1096
- purpose: "wallet-buy",
1097
- walletRootId: null,
1098
- });
1099
905
  const normalizedDomainName = normalizeDomainName(options.domainName);
1100
- try {
1101
- const miningPreemption = await pauseMiningForWalletMutation({
1102
- paths,
1103
- reason: "wallet-buy",
1104
- });
1105
- const readContext = await (options.openReadContext ?? openWalletReadContext)({
1106
- dataDir: options.dataDir,
1107
- databasePath: options.databasePath,
1108
- secretProvider: provider,
1109
- walletControlLockHeld: true,
1110
- paths,
1111
- });
1112
- try {
906
+ const execution = await executeWalletMutationOperation({
907
+ ...options,
908
+ controlLockPurpose: "wallet-buy",
909
+ preemptionReason: "wallet-buy",
910
+ resolveOperation(readContext) {
1113
911
  const operation = resolveBuyOperation(readContext, normalizedDomainName, options.fromIdentity ?? null);
1114
- const snapshot = readContext.snapshot;
1115
912
  const model = readContext.model;
1116
913
  const sellerScriptPubKeyHex = Buffer.from(operation.chainDomain.ownerScriptPubKey).toString("hex");
1117
914
  const sellerAddress = sellerScriptPubKeyHex === model.walletScriptPubKeyHex ? model.walletAddress : null;
1118
- const resolvedBuyer = {
1119
- selector: operation.buyerSelector,
1120
- localIndex: operation.sender.localIndex,
1121
- scriptPubKeyHex: operation.sender.scriptPubKeyHex,
1122
- address: operation.sender.address,
1123
- };
1124
- const resolvedSeller = {
1125
- scriptPubKeyHex: sellerScriptPubKeyHex,
1126
- address: sellerAddress,
915
+ return {
916
+ ...operation,
917
+ normalizedDomainName,
918
+ sellerScriptPubKeyHex,
919
+ resolvedBuyer: {
920
+ selector: operation.buyerSelector,
921
+ localIndex: operation.sender.localIndex,
922
+ scriptPubKeyHex: operation.sender.scriptPubKeyHex,
923
+ address: operation.sender.address,
924
+ },
925
+ resolvedSeller: {
926
+ scriptPubKeyHex: sellerScriptPubKeyHex,
927
+ address: sellerAddress,
928
+ },
1127
929
  };
1128
- const intentFingerprintHex = createIntentFingerprint([
930
+ },
931
+ createIntentFingerprint(operation) {
932
+ return createIntentFingerprint([
1129
933
  "buy",
1130
934
  operation.state.walletRootId,
1131
- normalizedDomainName,
935
+ operation.normalizedDomainName,
1132
936
  operation.sender.scriptPubKeyHex,
1133
937
  operation.listingPriceCogtoshi.toString(),
1134
938
  ]);
1135
- const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1136
- dataDir: options.dataDir,
1137
- chain: "main",
1138
- startHeight: 0,
1139
- walletRootId: operation.state.walletRootId,
1140
- });
1141
- const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1142
- const walletName = operation.state.managedCoreWallet.walletName;
1143
- const feeSelection = await resolveWalletMutationFeeSelection({
1144
- rpc,
1145
- feeRateSatVb: options.feeRateSatVb ?? null,
1146
- });
1147
- const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
1148
- let workingState = operation.state;
1149
- let replacementFixedInputs = null;
1150
- if (existingMutation !== null) {
1151
- const reconciled = await reconcilePendingMutation({
1152
- state: operation.state,
1153
- mutation: existingMutation,
1154
- provider,
1155
- nowUnixMs,
1156
- paths,
1157
- rpc,
1158
- walletName,
1159
- context: readContext,
1160
- });
1161
- workingState = reconciled.state;
1162
- if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
1163
- const reuse = await resolvePendingMutationReuseDecision({
1164
- rpc,
1165
- walletName,
1166
- mutation: reconciled.mutation,
1167
- nextFeeSelection: feeSelection,
1168
- });
1169
- if (reuse.reuseExisting) {
1170
- return {
1171
- kind: "buy",
1172
- domainName: normalizedDomainName,
1173
- txid: reconciled.mutation.attemptedTxid ?? "unknown",
1174
- status: reconciled.resolution,
1175
- reusedExisting: true,
1176
- listedPriceCogtoshi: operation.listingPriceCogtoshi,
1177
- resolvedBuyer,
1178
- resolvedSeller,
1179
- fees: reuse.fees,
1180
- };
1181
- }
1182
- replacementFixedInputs = reuse.replacementFixedInputs;
1183
- }
1184
- if (reconciled.resolution === "repair-required") {
1185
- throw new Error("wallet_buy_repair_required");
1186
- }
939
+ },
940
+ async resolveExistingMutation({ operation, existingMutation, execution }) {
941
+ if (existingMutation === null) {
942
+ return { state: operation.state, replacementFixedInputs: null, result: null };
1187
943
  }
1188
- await confirmBuy(options.prompter, normalizedDomainName, operation.buyerSelector, operation.sender, sellerScriptPubKeyHex, sellerAddress, operation.listingPriceCogtoshi, options.assumeYes);
1189
- let nextState = upsertPendingMutation(workingState, createDraftMutation({
1190
- kind: "buy",
1191
- domainName: normalizedDomainName,
1192
- sender: operation.sender,
1193
- priceCogtoshi: operation.listingPriceCogtoshi,
1194
- intentFingerprintHex,
1195
- nowUnixMs,
1196
- feeSelection,
1197
- existing: existingMutation,
1198
- }));
1199
- nextState = {
1200
- ...nextState,
1201
- stateRevision: nextState.stateRevision + 1,
1202
- lastWrittenAtUnixMs: nowUnixMs,
1203
- };
1204
- await saveWalletStatePreservingUnlock({
1205
- state: nextState,
1206
- provider,
1207
- nowUnixMs,
1208
- paths,
944
+ return resolveExistingWalletMutation({
945
+ existingMutation,
946
+ execution,
947
+ repairRequiredErrorCode: "wallet_buy_repair_required",
948
+ reconcileExistingMutation: (mutation) => reconcilePendingMutation({
949
+ state: operation.state,
950
+ mutation,
951
+ provider: execution.provider,
952
+ nowUnixMs: execution.nowUnixMs,
953
+ paths: execution.paths,
954
+ rpc: execution.rpc,
955
+ walletName: execution.walletName,
956
+ context: execution.readContext,
957
+ }),
958
+ createReuseResult: ({ mutation, resolution, fees }) => ({
959
+ kind: "buy",
960
+ domainName: operation.normalizedDomainName,
961
+ txid: mutation.attemptedTxid ?? "unknown",
962
+ status: resolution,
963
+ reusedExisting: true,
964
+ listedPriceCogtoshi: operation.listingPriceCogtoshi,
965
+ resolvedBuyer: operation.resolvedBuyer,
966
+ resolvedSeller: operation.resolvedSeller,
967
+ fees,
968
+ }),
1209
969
  });
1210
- const buildPreparation = await prepareDomainMarketBuildState({
1211
- rpc,
1212
- walletName,
1213
- state: nextState,
1214
- provider,
1215
- nowUnixMs,
1216
- paths,
970
+ },
971
+ confirm({ operation }) {
972
+ return confirmBuy(options.prompter, operation.normalizedDomainName, operation.buyerSelector, operation.sender, operation.sellerScriptPubKeyHex, operation.resolvedSeller.address, operation.listingPriceCogtoshi, options.assumeYes);
973
+ },
974
+ createDraftMutation({ operation, existingMutation, execution, intentFingerprintHex }) {
975
+ return {
976
+ mutation: createDraftMutation({
977
+ kind: "buy",
978
+ domainName: operation.normalizedDomainName,
979
+ sender: operation.sender,
980
+ priceCogtoshi: operation.listingPriceCogtoshi,
981
+ intentFingerprintHex,
982
+ nowUnixMs: execution.nowUnixMs,
983
+ feeSelection: execution.feeSelection,
984
+ existing: existingMutation,
985
+ }),
986
+ prepared: null,
987
+ };
988
+ },
989
+ async prepareBuildState({ state, execution }) {
990
+ return (await prepareDomainMarketBuildState({
991
+ rpc: execution.rpc,
992
+ walletName: execution.walletName,
993
+ state,
994
+ provider: execution.provider,
995
+ nowUnixMs: execution.nowUnixMs,
996
+ paths: execution.paths,
1217
997
  preflightCoinControl: false,
1218
- });
1219
- nextState = buildPreparation.state;
998
+ })).state;
999
+ },
1000
+ async build({ operation, state, execution, replacementFixedInputs }) {
1220
1001
  const buyPlan = buildPlanForDomainOperation({
1221
- state: nextState,
1222
- allUtxos: buildPreparation.allUtxos,
1002
+ state,
1003
+ allUtxos: await execution.rpc.listUnspent(execution.walletName, 1),
1223
1004
  sender: operation.sender,
1224
1005
  opReturnData: serializeDomainBuy(operation.chainDomain.domainId, operation.listingPriceCogtoshi).opReturnData,
1225
1006
  errorPrefix: "wallet_buy",
1226
1007
  });
1227
- const built = await buildTransaction({
1228
- rpc,
1229
- walletName,
1230
- state: nextState,
1008
+ return buildTransaction({
1009
+ rpc: execution.rpc,
1010
+ walletName: execution.walletName,
1011
+ state,
1231
1012
  plan: {
1232
1013
  ...buyPlan,
1233
1014
  fixedInputs: mergeFixedWalletInputs(buyPlan.fixedInputs, replacementFixedInputs),
1234
1015
  },
1235
- feeRateSatVb: feeSelection.feeRateSatVb,
1016
+ feeRateSatVb: execution.feeSelection.feeRateSatVb,
1236
1017
  });
1018
+ },
1019
+ beforePublish({ operation }) {
1237
1020
  const currentSellerHex = Buffer.from(operation.chainDomain.ownerScriptPubKey).toString("hex");
1238
- if (currentSellerHex !== sellerScriptPubKeyHex) {
1239
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1021
+ if (currentSellerHex !== operation.sellerScriptPubKeyHex) {
1240
1022
  throw new Error("wallet_buy_stale_listing_owner");
1241
1023
  }
1242
- const broadcasting = updateMutationRecord(nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex), "broadcasting", nowUnixMs, {
1243
- attemptedTxid: built.txid,
1244
- attemptedWtxid: built.wtxid,
1245
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
1246
- });
1247
- nextState = {
1248
- ...upsertPendingMutation(nextState, broadcasting),
1249
- stateRevision: nextState.stateRevision + 1,
1250
- lastWrittenAtUnixMs: nowUnixMs,
1251
- };
1252
- await saveWalletStatePreservingUnlock({
1253
- state: nextState,
1254
- provider,
1255
- nowUnixMs,
1256
- paths,
1257
- });
1258
- if (snapshot.tip?.height !== (await rpc.getBlockchainInfo()).blocks) {
1259
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1260
- throw new Error("wallet_buy_tip_mismatch");
1261
- }
1262
- try {
1263
- await rpc.sendRawTransaction(built.rawHex);
1264
- }
1265
- catch (error) {
1266
- if (!isAlreadyAcceptedError(error)) {
1267
- if (isBroadcastUnknownError(error)) {
1268
- const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", nowUnixMs, {
1269
- attemptedTxid: built.txid,
1270
- attemptedWtxid: built.wtxid,
1271
- temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
1272
- });
1273
- nextState = {
1274
- ...upsertPendingMutation(nextState, unknown),
1275
- stateRevision: nextState.stateRevision + 1,
1276
- lastWrittenAtUnixMs: nowUnixMs,
1277
- };
1278
- await saveWalletStatePreservingUnlock({
1279
- state: nextState,
1280
- provider,
1281
- nowUnixMs,
1282
- paths,
1283
- });
1284
- throw new Error("wallet_buy_broadcast_unknown");
1285
- }
1286
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1287
- const canceled = updateMutationRecord(broadcasting, "canceled", nowUnixMs, {
1024
+ return Promise.resolve();
1025
+ },
1026
+ publish({ operation, state, execution, built, mutation }) {
1027
+ return publishWalletMutation({
1028
+ rpc: execution.rpc,
1029
+ walletName: execution.walletName,
1030
+ snapshotHeight: execution.readContext.snapshot?.tip?.height ?? null,
1031
+ built,
1032
+ mutation,
1033
+ state,
1034
+ provider: execution.provider,
1035
+ nowUnixMs: execution.nowUnixMs,
1036
+ paths: execution.paths,
1037
+ errorPrefix: "wallet_buy",
1038
+ async afterAccepted({ state: acceptedState, broadcastingMutation, built, nowUnixMs }) {
1039
+ const finalStatus = getBuyStatusAfterAcceptance({
1040
+ snapshot: execution.readContext.snapshot,
1041
+ domainName: operation.normalizedDomainName,
1042
+ buyerScriptPubKeyHex: operation.sender.scriptPubKeyHex,
1043
+ });
1044
+ const finalMutation = updateMutationRecord(broadcastingMutation, finalStatus, nowUnixMs, {
1288
1045
  attemptedTxid: built.txid,
1289
1046
  attemptedWtxid: built.wtxid,
1290
1047
  temporaryBuilderLockedOutpoints: [],
1291
1048
  });
1292
- nextState = {
1293
- ...upsertPendingMutation(nextState, canceled),
1294
- stateRevision: nextState.stateRevision + 1,
1295
- lastWrittenAtUnixMs: nowUnixMs,
1049
+ return {
1050
+ state: reserveTransferredDomainRecord({
1051
+ state: upsertPendingMutation(acceptedState, finalMutation),
1052
+ domainName: operation.normalizedDomainName,
1053
+ domainId: operation.chainDomain.domainId,
1054
+ currentOwnerScriptPubKeyHex: operation.sender.scriptPubKeyHex,
1055
+ nowUnixMs,
1056
+ }),
1057
+ mutation: finalMutation,
1058
+ status: finalStatus,
1296
1059
  };
1297
- await saveWalletStatePreservingUnlock({
1298
- state: nextState,
1299
- provider,
1300
- nowUnixMs,
1301
- paths,
1302
- });
1303
- throw error;
1304
- }
1305
- }
1306
- await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
1307
- const finalStatus = getBuyStatusAfterAcceptance({
1308
- snapshot: readContext.snapshot,
1309
- domainName: normalizedDomainName,
1310
- buyerScriptPubKeyHex: operation.sender.scriptPubKeyHex,
1311
- });
1312
- const finalMutation = updateMutationRecord(broadcasting, finalStatus, nowUnixMs, {
1313
- attemptedTxid: built.txid,
1314
- attemptedWtxid: built.wtxid,
1315
- temporaryBuilderLockedOutpoints: [],
1316
- });
1317
- nextState = reserveTransferredDomainRecord({
1318
- state: upsertPendingMutation(nextState, finalMutation),
1319
- domainName: normalizedDomainName,
1320
- domainId: operation.chainDomain.domainId,
1321
- currentOwnerScriptPubKeyHex: operation.sender.scriptPubKeyHex,
1322
- nowUnixMs,
1323
- });
1324
- nextState = {
1325
- ...nextState,
1326
- stateRevision: nextState.stateRevision + 1,
1327
- lastWrittenAtUnixMs: nowUnixMs,
1328
- };
1329
- await saveWalletStatePreservingUnlock({
1330
- state: nextState,
1331
- provider,
1332
- nowUnixMs,
1333
- paths,
1060
+ },
1334
1061
  });
1062
+ },
1063
+ createResult({ operation, mutation, built, status, reusedExisting, fees }) {
1335
1064
  return {
1336
1065
  kind: "buy",
1337
- domainName: normalizedDomainName,
1338
- txid: built.txid,
1339
- status: finalStatus,
1340
- reusedExisting: false,
1066
+ domainName: operation.normalizedDomainName,
1067
+ txid: mutation.attemptedTxid ?? built?.txid ?? "unknown",
1068
+ status: status,
1069
+ reusedExisting,
1341
1070
  listedPriceCogtoshi: operation.listingPriceCogtoshi,
1342
- resolvedBuyer,
1343
- resolvedSeller,
1344
- fees: createBuiltWalletMutationFeeSummary({
1345
- selection: feeSelection,
1346
- built,
1347
- }),
1071
+ resolvedBuyer: operation.resolvedBuyer,
1072
+ resolvedSeller: operation.resolvedSeller,
1073
+ fees,
1348
1074
  };
1349
- }
1350
- finally {
1351
- await readContext.close();
1352
- await miningPreemption.release();
1353
- }
1354
- }
1355
- finally {
1356
- await controlLock.release();
1357
- }
1075
+ },
1076
+ });
1077
+ return execution.result;
1358
1078
  }