@cogcoin/client 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/dist/app-paths.d.ts +38 -0
  4. package/dist/app-paths.js +121 -0
  5. package/dist/art/banner.txt +13 -0
  6. package/dist/art/scroll.txt +13 -0
  7. package/dist/art/train-car.txt +6 -0
  8. package/dist/art/train-smoke.txt +6 -0
  9. package/dist/art/train.txt +6 -0
  10. package/dist/bitcoind/bootstrap/chainstate.d.ts +4 -0
  11. package/dist/bitcoind/bootstrap/chainstate.js +13 -0
  12. package/dist/bitcoind/bootstrap/constants.d.ts +7 -0
  13. package/dist/bitcoind/bootstrap/constants.js +12 -0
  14. package/dist/bitcoind/bootstrap/controller.d.ts +29 -0
  15. package/dist/bitcoind/bootstrap/controller.js +101 -0
  16. package/dist/bitcoind/bootstrap/download.d.ts +2 -0
  17. package/dist/bitcoind/bootstrap/download.js +196 -0
  18. package/dist/bitcoind/bootstrap/headers.d.ts +13 -0
  19. package/dist/bitcoind/bootstrap/headers.js +61 -0
  20. package/dist/bitcoind/bootstrap/paths.d.ts +4 -0
  21. package/dist/bitcoind/bootstrap/paths.js +15 -0
  22. package/dist/bitcoind/bootstrap/snapshot-file.d.ts +7 -0
  23. package/dist/bitcoind/bootstrap/snapshot-file.js +42 -0
  24. package/dist/bitcoind/bootstrap/state.d.ts +40 -0
  25. package/dist/bitcoind/bootstrap/state.js +70 -0
  26. package/dist/bitcoind/bootstrap/types.d.ts +28 -0
  27. package/dist/bitcoind/bootstrap/types.js +1 -0
  28. package/dist/bitcoind/bootstrap.d.ts +8 -0
  29. package/dist/bitcoind/bootstrap.js +7 -0
  30. package/dist/bitcoind/client/factory.d.ts +3 -0
  31. package/dist/bitcoind/client/factory.js +57 -0
  32. package/dist/bitcoind/client/follow-block-times.d.ts +8 -0
  33. package/dist/bitcoind/client/follow-block-times.js +25 -0
  34. package/dist/bitcoind/client/follow-loop.d.ts +10 -0
  35. package/dist/bitcoind/client/follow-loop.js +57 -0
  36. package/dist/bitcoind/client/internal-types.d.ts +63 -0
  37. package/dist/bitcoind/client/internal-types.js +18 -0
  38. package/dist/bitcoind/client/managed-client.d.ts +20 -0
  39. package/dist/bitcoind/client/managed-client.js +197 -0
  40. package/dist/bitcoind/client/rate-tracker.d.ts +2 -0
  41. package/dist/bitcoind/client/rate-tracker.js +24 -0
  42. package/dist/bitcoind/client/sync-engine.d.ts +3 -0
  43. package/dist/bitcoind/client/sync-engine.js +143 -0
  44. package/dist/bitcoind/client.d.ts +1 -0
  45. package/dist/bitcoind/client.js +1 -0
  46. package/dist/bitcoind/errors.d.ts +1 -0
  47. package/dist/bitcoind/errors.js +49 -0
  48. package/dist/bitcoind/index.d.ts +2 -0
  49. package/dist/bitcoind/index.js +1 -0
  50. package/dist/bitcoind/indexer-daemon-main.d.ts +1 -0
  51. package/dist/bitcoind/indexer-daemon-main.js +472 -0
  52. package/dist/bitcoind/indexer-daemon.d.ts +107 -0
  53. package/dist/bitcoind/indexer-daemon.js +391 -0
  54. package/dist/bitcoind/node.d.ts +8 -0
  55. package/dist/bitcoind/node.js +219 -0
  56. package/dist/bitcoind/normalize.d.ts +3 -0
  57. package/dist/bitcoind/normalize.js +47 -0
  58. package/dist/bitcoind/progress/assets.d.ts +10 -0
  59. package/dist/bitcoind/progress/assets.js +90 -0
  60. package/dist/bitcoind/progress/constants.d.ts +48 -0
  61. package/dist/bitcoind/progress/constants.js +53 -0
  62. package/dist/bitcoind/progress/controller.d.ts +28 -0
  63. package/dist/bitcoind/progress/controller.js +188 -0
  64. package/dist/bitcoind/progress/follow-scene.d.ts +40 -0
  65. package/dist/bitcoind/progress/follow-scene.js +367 -0
  66. package/dist/bitcoind/progress/formatting.d.ts +23 -0
  67. package/dist/bitcoind/progress/formatting.js +227 -0
  68. package/dist/bitcoind/progress/quote-scene.d.ts +4 -0
  69. package/dist/bitcoind/progress/quote-scene.js +137 -0
  70. package/dist/bitcoind/progress/train-scene.d.ts +9 -0
  71. package/dist/bitcoind/progress/train-scene.js +92 -0
  72. package/dist/bitcoind/progress/tty-renderer.d.ts +18 -0
  73. package/dist/bitcoind/progress/tty-renderer.js +150 -0
  74. package/dist/bitcoind/progress.d.ts +7 -0
  75. package/dist/bitcoind/progress.js +7 -0
  76. package/dist/bitcoind/quotes.d.ts +24 -0
  77. package/dist/bitcoind/quotes.js +195 -0
  78. package/dist/bitcoind/rpc.d.ts +71 -0
  79. package/dist/bitcoind/rpc.js +322 -0
  80. package/dist/bitcoind/service-paths.d.ts +19 -0
  81. package/dist/bitcoind/service-paths.js +49 -0
  82. package/dist/bitcoind/service.d.ts +40 -0
  83. package/dist/bitcoind/service.js +735 -0
  84. package/dist/bitcoind/testing.d.ts +9 -0
  85. package/dist/bitcoind/testing.js +9 -0
  86. package/dist/bitcoind/types.d.ts +396 -0
  87. package/dist/bitcoind/types.js +3 -0
  88. package/dist/bytes.d.ts +9 -0
  89. package/dist/bytes.js +36 -0
  90. package/dist/cli/commands/follow.d.ts +2 -0
  91. package/dist/cli/commands/follow.js +43 -0
  92. package/dist/cli/commands/mining-admin.d.ts +2 -0
  93. package/dist/cli/commands/mining-admin.js +92 -0
  94. package/dist/cli/commands/mining-read.d.ts +2 -0
  95. package/dist/cli/commands/mining-read.js +173 -0
  96. package/dist/cli/commands/mining-runtime.d.ts +2 -0
  97. package/dist/cli/commands/mining-runtime.js +108 -0
  98. package/dist/cli/commands/status.d.ts +2 -0
  99. package/dist/cli/commands/status.js +31 -0
  100. package/dist/cli/commands/sync.d.ts +2 -0
  101. package/dist/cli/commands/sync.js +52 -0
  102. package/dist/cli/commands/wallet-admin.d.ts +2 -0
  103. package/dist/cli/commands/wallet-admin.js +175 -0
  104. package/dist/cli/commands/wallet-mutation.d.ts +2 -0
  105. package/dist/cli/commands/wallet-mutation.js +681 -0
  106. package/dist/cli/commands/wallet-read.d.ts +2 -0
  107. package/dist/cli/commands/wallet-read.js +265 -0
  108. package/dist/cli/context.d.ts +3 -0
  109. package/dist/cli/context.js +75 -0
  110. package/dist/cli/io.d.ts +3 -0
  111. package/dist/cli/io.js +12 -0
  112. package/dist/cli/mining-format.d.ts +5 -0
  113. package/dist/cli/mining-format.js +156 -0
  114. package/dist/cli/mining-json.d.ts +49 -0
  115. package/dist/cli/mining-json.js +89 -0
  116. package/dist/cli/mutation-command-groups.d.ts +15 -0
  117. package/dist/cli/mutation-command-groups.js +71 -0
  118. package/dist/cli/mutation-json.d.ts +430 -0
  119. package/dist/cli/mutation-json.js +311 -0
  120. package/dist/cli/mutation-resolved-json.d.ts +124 -0
  121. package/dist/cli/mutation-resolved-json.js +129 -0
  122. package/dist/cli/mutation-success.d.ts +20 -0
  123. package/dist/cli/mutation-success.js +47 -0
  124. package/dist/cli/mutation-text-format.d.ts +22 -0
  125. package/dist/cli/mutation-text-format.js +171 -0
  126. package/dist/cli/mutation-text-write.d.ts +13 -0
  127. package/dist/cli/mutation-text-write.js +16 -0
  128. package/dist/cli/output.d.ts +185 -0
  129. package/dist/cli/output.js +1085 -0
  130. package/dist/cli/parse.d.ts +3 -0
  131. package/dist/cli/parse.js +971 -0
  132. package/dist/cli/preview-json.d.ts +416 -0
  133. package/dist/cli/preview-json.js +293 -0
  134. package/dist/cli/prompt.d.ts +3 -0
  135. package/dist/cli/prompt.js +33 -0
  136. package/dist/cli/read-json.d.ts +187 -0
  137. package/dist/cli/read-json.js +675 -0
  138. package/dist/cli/runner.d.ts +2 -0
  139. package/dist/cli/runner.js +129 -0
  140. package/dist/cli/signals.d.ts +3 -0
  141. package/dist/cli/signals.js +63 -0
  142. package/dist/cli/status-format.d.ts +2 -0
  143. package/dist/cli/status-format.js +48 -0
  144. package/dist/cli/types.d.ts +148 -0
  145. package/dist/cli/types.js +2 -0
  146. package/dist/cli/wallet-format.d.ts +29 -0
  147. package/dist/cli/wallet-format.js +637 -0
  148. package/dist/cli/workflow-hints.d.ts +13 -0
  149. package/dist/cli/workflow-hints.js +94 -0
  150. package/dist/cli-runner.d.ts +3 -0
  151. package/dist/cli-runner.js +3 -0
  152. package/dist/cli.d.ts +2 -0
  153. package/dist/cli.js +6 -0
  154. package/dist/client/default-client.d.ts +11 -0
  155. package/dist/client/default-client.js +118 -0
  156. package/dist/client/factory.d.ts +2 -0
  157. package/dist/client/factory.js +15 -0
  158. package/dist/client/initialization.d.ts +6 -0
  159. package/dist/client/initialization.js +30 -0
  160. package/dist/client/persistence.d.ts +5 -0
  161. package/dist/client/persistence.js +28 -0
  162. package/dist/client/store-adapter.d.ts +3 -0
  163. package/dist/client/store-adapter.js +20 -0
  164. package/dist/client.d.ts +2 -0
  165. package/dist/client.js +2 -0
  166. package/dist/index.d.ts +2 -0
  167. package/dist/index.js +1 -0
  168. package/dist/passive-status.d.ts +36 -0
  169. package/dist/passive-status.js +100 -0
  170. package/dist/sqlite/better-sqlite3.d.ts +26 -0
  171. package/dist/sqlite/better-sqlite3.js +4 -0
  172. package/dist/sqlite/checkpoints.d.ts +11 -0
  173. package/dist/sqlite/checkpoints.js +27 -0
  174. package/dist/sqlite/driver.d.ts +17 -0
  175. package/dist/sqlite/driver.js +98 -0
  176. package/dist/sqlite/index.d.ts +4 -0
  177. package/dist/sqlite/index.js +9 -0
  178. package/dist/sqlite/migrate.d.ts +2 -0
  179. package/dist/sqlite/migrate.js +37 -0
  180. package/dist/sqlite/store.d.ts +3 -0
  181. package/dist/sqlite/store.js +122 -0
  182. package/dist/sqlite/tip-meta.d.ts +26 -0
  183. package/dist/sqlite/tip-meta.js +97 -0
  184. package/dist/sqlite/types.d.ts +10 -0
  185. package/dist/sqlite/types.js +1 -0
  186. package/dist/types.d.ts +55 -0
  187. package/dist/types.js +1 -0
  188. package/dist/wallet/archive.d.ts +4 -0
  189. package/dist/wallet/archive.js +39 -0
  190. package/dist/wallet/cogop/constants.d.ts +32 -0
  191. package/dist/wallet/cogop/constants.js +32 -0
  192. package/dist/wallet/cogop/index.d.ts +32 -0
  193. package/dist/wallet/cogop/index.js +213 -0
  194. package/dist/wallet/cogop/numeric.d.ts +3 -0
  195. package/dist/wallet/cogop/numeric.js +24 -0
  196. package/dist/wallet/cogop/scriptpubkey.d.ts +2 -0
  197. package/dist/wallet/cogop/scriptpubkey.js +13 -0
  198. package/dist/wallet/cogop/validate-name.d.ts +2 -0
  199. package/dist/wallet/cogop/validate-name.js +18 -0
  200. package/dist/wallet/fs/atomic.d.ts +6 -0
  201. package/dist/wallet/fs/atomic.js +46 -0
  202. package/dist/wallet/fs/lock.d.ts +19 -0
  203. package/dist/wallet/fs/lock.js +61 -0
  204. package/dist/wallet/fs/status-file.d.ts +1 -0
  205. package/dist/wallet/fs/status-file.js +4 -0
  206. package/dist/wallet/lifecycle.d.ts +193 -0
  207. package/dist/wallet/lifecycle.js +1475 -0
  208. package/dist/wallet/material.d.ts +45 -0
  209. package/dist/wallet/material.js +118 -0
  210. package/dist/wallet/mining/config.d.ts +18 -0
  211. package/dist/wallet/mining/config.js +44 -0
  212. package/dist/wallet/mining/constants.d.ts +24 -0
  213. package/dist/wallet/mining/constants.js +24 -0
  214. package/dist/wallet/mining/control.d.ts +53 -0
  215. package/dist/wallet/mining/control.js +758 -0
  216. package/dist/wallet/mining/coordination.d.ts +40 -0
  217. package/dist/wallet/mining/coordination.js +121 -0
  218. package/dist/wallet/mining/hook-protocol.d.ts +47 -0
  219. package/dist/wallet/mining/hook-protocol.js +161 -0
  220. package/dist/wallet/mining/hook-runner.d.ts +1 -0
  221. package/dist/wallet/mining/hook-runner.js +52 -0
  222. package/dist/wallet/mining/hooks.d.ts +38 -0
  223. package/dist/wallet/mining/hooks.js +520 -0
  224. package/dist/wallet/mining/index.d.ts +8 -0
  225. package/dist/wallet/mining/index.js +6 -0
  226. package/dist/wallet/mining/runner.d.ts +155 -0
  227. package/dist/wallet/mining/runner.js +2574 -0
  228. package/dist/wallet/mining/runtime-artifacts.d.ts +17 -0
  229. package/dist/wallet/mining/runtime-artifacts.js +166 -0
  230. package/dist/wallet/mining/sentences.d.ts +23 -0
  231. package/dist/wallet/mining/sentences.js +281 -0
  232. package/dist/wallet/mining/state.d.ts +9 -0
  233. package/dist/wallet/mining/state.js +75 -0
  234. package/dist/wallet/mining/types.d.ts +141 -0
  235. package/dist/wallet/mining/types.js +1 -0
  236. package/dist/wallet/mining/visualizer.d.ts +19 -0
  237. package/dist/wallet/mining/visualizer.js +134 -0
  238. package/dist/wallet/mining/worker-main.d.ts +1 -0
  239. package/dist/wallet/mining/worker-main.js +17 -0
  240. package/dist/wallet/read/context.d.ts +20 -0
  241. package/dist/wallet/read/context.js +532 -0
  242. package/dist/wallet/read/filter.d.ts +9 -0
  243. package/dist/wallet/read/filter.js +42 -0
  244. package/dist/wallet/read/index.d.ts +4 -0
  245. package/dist/wallet/read/index.js +3 -0
  246. package/dist/wallet/read/project.d.ts +11 -0
  247. package/dist/wallet/read/project.js +300 -0
  248. package/dist/wallet/read/types.d.ts +144 -0
  249. package/dist/wallet/read/types.js +1 -0
  250. package/dist/wallet/runtime.d.ts +26 -0
  251. package/dist/wallet/runtime.js +28 -0
  252. package/dist/wallet/state/crypto.d.ts +31 -0
  253. package/dist/wallet/state/crypto.js +127 -0
  254. package/dist/wallet/state/provider.d.ts +37 -0
  255. package/dist/wallet/state/provider.js +312 -0
  256. package/dist/wallet/state/session.d.ts +12 -0
  257. package/dist/wallet/state/session.js +23 -0
  258. package/dist/wallet/state/storage.d.ts +19 -0
  259. package/dist/wallet/state/storage.js +55 -0
  260. package/dist/wallet/tx/anchor.d.ts +40 -0
  261. package/dist/wallet/tx/anchor.js +1210 -0
  262. package/dist/wallet/tx/cog.d.ts +92 -0
  263. package/dist/wallet/tx/cog.js +1055 -0
  264. package/dist/wallet/tx/common.d.ts +89 -0
  265. package/dist/wallet/tx/common.js +156 -0
  266. package/dist/wallet/tx/confirm.d.ts +15 -0
  267. package/dist/wallet/tx/confirm.js +24 -0
  268. package/dist/wallet/tx/domain-admin.d.ts +105 -0
  269. package/dist/wallet/tx/domain-admin.js +869 -0
  270. package/dist/wallet/tx/domain-market.d.ts +112 -0
  271. package/dist/wallet/tx/domain-market.js +1365 -0
  272. package/dist/wallet/tx/field.d.ts +101 -0
  273. package/dist/wallet/tx/field.js +1853 -0
  274. package/dist/wallet/tx/identity-selector.d.ts +12 -0
  275. package/dist/wallet/tx/identity-selector.js +52 -0
  276. package/dist/wallet/tx/index.d.ts +7 -0
  277. package/dist/wallet/tx/index.js +7 -0
  278. package/dist/wallet/tx/journal.d.ts +5 -0
  279. package/dist/wallet/tx/journal.js +31 -0
  280. package/dist/wallet/tx/register.d.ts +68 -0
  281. package/dist/wallet/tx/register.js +952 -0
  282. package/dist/wallet/tx/reputation.d.ts +72 -0
  283. package/dist/wallet/tx/reputation.js +693 -0
  284. package/dist/wallet/tx/targets.d.ts +7 -0
  285. package/dist/wallet/tx/targets.js +122 -0
  286. package/dist/wallet/types.d.ts +249 -0
  287. package/dist/wallet/types.js +1 -0
  288. package/dist/writing_quotes.json +1654 -0
  289. package/package.json +78 -0
@@ -0,0 +1,1210 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { encodeSentence } from "@cogcoin/scoring";
3
+ import { getListing, lookupDomain } from "@cogcoin/indexer/queries";
4
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
5
+ import { createRpcClient } from "../../bitcoind/node.js";
6
+ import { acquireFileLock } from "../fs/lock.js";
7
+ import { deriveWalletIdentityMaterial, } from "../material.js";
8
+ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
9
+ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
10
+ import { serializeDomainAnchor, serializeDomainTransfer, validateDomainName, } from "../cogop/index.js";
11
+ import { openWalletReadContext } from "../read/index.js";
12
+ import { assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, } from "./common.js";
13
+ const ACTIVE_FAMILY_STATUSES = new Set([
14
+ "draft",
15
+ "broadcasting",
16
+ "broadcast-unknown",
17
+ "live",
18
+ "repair-required",
19
+ ]);
20
+ function normalizeDomainName(domainName) {
21
+ const normalized = domainName.trim().toLowerCase();
22
+ if (normalized.length === 0) {
23
+ throw new Error("wallet_anchor_missing_domain");
24
+ }
25
+ validateDomainName(normalized);
26
+ return normalized;
27
+ }
28
+ function encodeOpReturnScript(payload) {
29
+ if (payload.length <= 75) {
30
+ return Buffer.concat([
31
+ Buffer.from([0x6a, payload.length]),
32
+ Buffer.from(payload),
33
+ ]).toString("hex");
34
+ }
35
+ return Buffer.concat([
36
+ Buffer.from([0x6a, 0x4c, payload.length]),
37
+ Buffer.from(payload),
38
+ ]).toString("hex");
39
+ }
40
+ function satsToBtcNumber(value) {
41
+ return Number(value) / 100_000_000;
42
+ }
43
+ function valueToSats(value) {
44
+ const text = typeof value === "number" ? value.toFixed(8) : value;
45
+ const match = /^(-?)(\d+)(?:\.(\d{0,8}))?$/.exec(text.trim());
46
+ if (match == null) {
47
+ throw new Error(`wallet_anchor_invalid_amount_${text}`);
48
+ }
49
+ const sign = match[1] === "-" ? -1n : 1n;
50
+ const whole = BigInt(match[2] ?? "0");
51
+ const fraction = BigInt((match[3] ?? "").padEnd(8, "0"));
52
+ return sign * ((whole * 100000000n) + fraction);
53
+ }
54
+ function createIntentFingerprint(parts) {
55
+ return createHash("sha256")
56
+ .update(parts.map((part) => String(part)).join("\n"))
57
+ .digest("hex");
58
+ }
59
+ function isSpendableConfirmedUtxo(entry) {
60
+ return entry.confirmations >= 1
61
+ && entry.spendable !== false
62
+ && entry.safe !== false;
63
+ }
64
+ function sortUtxos(entries) {
65
+ return entries
66
+ .slice()
67
+ .sort((left, right) => right.amount - left.amount
68
+ || left.txid.localeCompare(right.txid)
69
+ || left.vout - right.vout);
70
+ }
71
+ function createReservedIdentityRecord(target) {
72
+ return {
73
+ index: target.localIndex,
74
+ scriptPubKeyHex: target.scriptPubKeyHex,
75
+ address: target.address,
76
+ status: "dedicated",
77
+ assignedDomainNames: [],
78
+ };
79
+ }
80
+ function withUpdatedAssignedDomain(options) {
81
+ return options.identities.map((identity) => {
82
+ let assigned = identity.assignedDomainNames.filter((name) => name !== options.domainName);
83
+ if (identity.index === options.targetLocalIndex) {
84
+ assigned = [...assigned, options.domainName];
85
+ }
86
+ if (identity.index !== options.sourceLocalIndex && identity.index !== options.targetLocalIndex) {
87
+ assigned = identity.assignedDomainNames.slice();
88
+ }
89
+ return {
90
+ ...identity,
91
+ assignedDomainNames: assigned.sort((left, right) => left.localeCompare(right)),
92
+ };
93
+ });
94
+ }
95
+ function upsertProactiveFamily(state, family) {
96
+ const families = state.proactiveFamilies.slice();
97
+ const existingIndex = families.findIndex((entry) => entry.familyId === family.familyId);
98
+ if (existingIndex >= 0) {
99
+ families[existingIndex] = family;
100
+ }
101
+ else {
102
+ families.push(family);
103
+ }
104
+ return {
105
+ ...state,
106
+ proactiveFamilies: families,
107
+ };
108
+ }
109
+ function findAnchorFamilyByIntent(state, intentFingerprintHex) {
110
+ return state.proactiveFamilies.find((family) => family.type === "anchor" && family.intentFingerprintHex === intentFingerprintHex) ?? null;
111
+ }
112
+ function findActiveAnchorFamilyByDomain(state, domainName) {
113
+ return state.proactiveFamilies.find((family) => family.type === "anchor"
114
+ && family.domainName === domainName
115
+ && ACTIVE_FAMILY_STATUSES.has(family.status)) ?? null;
116
+ }
117
+ function findAnchorFamilyById(state, familyId) {
118
+ return state.proactiveFamilies.find((family) => family.familyId === familyId) ?? null;
119
+ }
120
+ function selectNextDedicatedIdentityTarget(state) {
121
+ const reservedIndices = new Set();
122
+ for (const domain of state.domains) {
123
+ if (domain.dedicatedIndex !== null) {
124
+ reservedIndices.add(domain.dedicatedIndex);
125
+ }
126
+ }
127
+ for (const family of state.proactiveFamilies) {
128
+ if (family.type === "anchor" && family.reservedDedicatedIndex !== null && family.reservedDedicatedIndex !== undefined) {
129
+ reservedIndices.add(family.reservedDedicatedIndex);
130
+ }
131
+ }
132
+ const startIndex = Math.max(1, state.nextDedicatedIndex);
133
+ for (let index = startIndex; index <= state.descriptor.rangeEnd; index += 1) {
134
+ if (reservedIndices.has(index)) {
135
+ continue;
136
+ }
137
+ const material = deriveWalletIdentityMaterial(state.keys.accountXprv, index);
138
+ return {
139
+ ...material,
140
+ localIndex: index,
141
+ };
142
+ }
143
+ throw new Error("wallet_anchor_no_fresh_dedicated_index");
144
+ }
145
+ function encodeFoundingMessage(foundingMessageText) {
146
+ const trimmed = foundingMessageText?.trim() ?? "";
147
+ if (trimmed === "") {
148
+ return Promise.resolve({
149
+ text: null,
150
+ payloadHex: null,
151
+ });
152
+ }
153
+ return encodeSentence(trimmed)
154
+ .then((payload) => ({
155
+ text: trimmed,
156
+ payloadHex: Buffer.from(payload).toString("hex"),
157
+ }))
158
+ .catch((error) => {
159
+ throw new Error(error instanceof Error ? `wallet_anchor_invalid_message_${error.message}` : "wallet_anchor_invalid_message");
160
+ });
161
+ }
162
+ function resolveAnchorOutpointForSender(state, senderIndex) {
163
+ const anchoredDomain = state.domains.find((domain) => domain.currentOwnerLocalIndex === senderIndex
164
+ && domain.canonicalChainStatus === "anchored"
165
+ && domain.currentCanonicalAnchorOutpoint !== null) ?? null;
166
+ if (anchoredDomain?.currentCanonicalAnchorOutpoint === null || anchoredDomain === null) {
167
+ return null;
168
+ }
169
+ return {
170
+ txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
171
+ vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
172
+ };
173
+ }
174
+ async function confirmAnchor(prompter, operation) {
175
+ prompter.writeLine(`You are anchoring "${operation.chainDomain.name}" onto dedicated index ${operation.targetIdentity.localIndex}.`);
176
+ prompter.writeLine("Anchoring is permanent chain state. This flow uses two transactions and is not rolled back automatically.");
177
+ prompter.writeLine(`Dedicated BTC address: ${operation.targetIdentity.address}`);
178
+ prompter.writeLine(`Dedicated Ethereum address: ${operation.targetIdentity.ethereumAddress}`);
179
+ prompter.writeLine(`Dedicated Nostr npub: ${operation.targetIdentity.nostrNpub}`);
180
+ if (operation.foundingMessageText !== null) {
181
+ prompter.writeLine("The founding message bytes will be public in mempool and on-chain.");
182
+ prompter.writeLine(`Founding message: ${operation.foundingMessageText}`);
183
+ }
184
+ if (operation.hadListing) {
185
+ prompter.writeLine("Warning: Tx1 will cancel the current listing for this domain.");
186
+ prompter.writeLine("That listing-cancel side effect is not rolled back automatically if Tx2 later fails.");
187
+ }
188
+ const answer = (await prompter.prompt("Type the domain name to continue: ")).trim();
189
+ if (answer !== operation.chainDomain.name) {
190
+ throw new Error("wallet_anchor_confirmation_rejected");
191
+ }
192
+ }
193
+ function resolveAnchorOperation(context, domainName, foundingMessageText, foundingMessagePayloadHex) {
194
+ assertWalletMutationContextReady(context, "wallet_anchor");
195
+ const chainDomain = lookupDomain(context.snapshot.state, domainName);
196
+ if (chainDomain === null) {
197
+ throw new Error("wallet_anchor_domain_not_found");
198
+ }
199
+ if (chainDomain.anchored) {
200
+ throw new Error("wallet_anchor_domain_already_anchored");
201
+ }
202
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
203
+ const senderIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === ownerHex) ?? null;
204
+ if (senderIdentity === null || senderIdentity.address === null) {
205
+ throw new Error("wallet_anchor_owner_not_locally_controlled");
206
+ }
207
+ if (senderIdentity.readOnly) {
208
+ throw new Error("wallet_anchor_owner_read_only");
209
+ }
210
+ const sourceAnchorOutpoint = senderIdentity.index === context.localState.state.fundingIndex
211
+ ? null
212
+ : resolveAnchorOutpointForSender(context.localState.state, senderIdentity.index);
213
+ if (senderIdentity.index !== context.localState.state.fundingIndex && sourceAnchorOutpoint === null) {
214
+ throw new Error("wallet_anchor_owner_identity_not_supported");
215
+ }
216
+ const targetIdentity = selectNextDedicatedIdentityTarget(context.localState.state);
217
+ return {
218
+ readContext: context,
219
+ state: context.localState.state,
220
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
221
+ sourceSender: {
222
+ localIndex: senderIdentity.index,
223
+ scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
224
+ address: senderIdentity.address,
225
+ },
226
+ sourceAnchorOutpoint,
227
+ chainDomain,
228
+ targetIdentity,
229
+ foundingMessageText,
230
+ foundingMessagePayloadHex,
231
+ hadListing: getListing(context.snapshot.state, chainDomain.domainId) !== null,
232
+ };
233
+ }
234
+ function createFamilyTransactionRecord() {
235
+ return {
236
+ status: "draft",
237
+ attemptedTxid: null,
238
+ attemptedWtxid: null,
239
+ temporaryBuilderLockedOutpoints: [],
240
+ rawHex: null,
241
+ };
242
+ }
243
+ function createDraftAnchorFamily(operation, nowUnixMs) {
244
+ return {
245
+ familyId: randomBytes(12).toString("hex"),
246
+ type: "anchor",
247
+ status: "draft",
248
+ intentFingerprintHex: createIntentFingerprint([
249
+ "anchor",
250
+ operation.state.walletRootId,
251
+ operation.chainDomain.name,
252
+ operation.sourceSender.scriptPubKeyHex,
253
+ operation.foundingMessagePayloadHex ?? "",
254
+ ]),
255
+ createdAtUnixMs: nowUnixMs,
256
+ lastUpdatedAtUnixMs: nowUnixMs,
257
+ domainName: operation.chainDomain.name,
258
+ domainId: operation.chainDomain.domainId,
259
+ sourceSenderLocalIndex: operation.sourceSender.localIndex,
260
+ sourceSenderScriptPubKeyHex: operation.sourceSender.scriptPubKeyHex,
261
+ reservedDedicatedIndex: operation.targetIdentity.localIndex,
262
+ reservedScriptPubKeyHex: operation.targetIdentity.scriptPubKeyHex,
263
+ foundingMessageText: operation.foundingMessageText,
264
+ foundingMessagePayloadHex: operation.foundingMessagePayloadHex,
265
+ listingCancelCommitted: false,
266
+ currentStep: "reserved",
267
+ tx1: createFamilyTransactionRecord(),
268
+ tx2: createFamilyTransactionRecord(),
269
+ };
270
+ }
271
+ function ensureReservedIdentity(identities, target) {
272
+ if (identities.some((identity) => identity.index === target.localIndex)) {
273
+ return identities;
274
+ }
275
+ return [...identities, createReservedIdentityRecord(target)]
276
+ .sort((left, right) => left.index - right.index);
277
+ }
278
+ function reserveAnchorFamilyState(state, family, target, foundingMessageText) {
279
+ const domains = state.domains.map((domain) => {
280
+ if (domain.name !== family.domainName) {
281
+ return domain;
282
+ }
283
+ return {
284
+ ...domain,
285
+ dedicatedIndex: target.localIndex,
286
+ localAnchorIntent: "reserved",
287
+ foundingMessageText: foundingMessageText ?? domain.foundingMessageText,
288
+ };
289
+ });
290
+ return {
291
+ ...upsertProactiveFamily(state, family),
292
+ nextDedicatedIndex: Math.max(state.nextDedicatedIndex, target.localIndex + 1),
293
+ identities: ensureReservedIdentity(state.identities, target),
294
+ domains,
295
+ };
296
+ }
297
+ function updateAnchorFamilyState(options) {
298
+ const nextFamily = {
299
+ ...options.family,
300
+ status: options.status,
301
+ currentStep: options.currentStep,
302
+ lastUpdatedAtUnixMs: options.nowUnixMs,
303
+ listingCancelCommitted: options.listingCancelCommitted ?? options.family.listingCancelCommitted,
304
+ tx1: options.tx1 ?? options.family.tx1 ?? createFamilyTransactionRecord(),
305
+ tx2: options.tx2 ?? options.family.tx2 ?? createFamilyTransactionRecord(),
306
+ };
307
+ let identities = ensureReservedIdentity(options.state.identities, options.target);
308
+ if (options.moveOwnershipToTarget) {
309
+ identities = withUpdatedAssignedDomain({
310
+ identities,
311
+ sourceLocalIndex: options.family.sourceSenderLocalIndex ?? null,
312
+ targetLocalIndex: options.target.localIndex,
313
+ domainName: options.family.domainName ?? "",
314
+ });
315
+ }
316
+ const domains = options.state.domains.map((domain) => {
317
+ if (domain.name !== options.family.domainName) {
318
+ return domain;
319
+ }
320
+ return {
321
+ ...domain,
322
+ dedicatedIndex: options.target.localIndex,
323
+ currentOwnerScriptPubKeyHex: options.moveOwnershipToTarget
324
+ ? options.target.scriptPubKeyHex
325
+ : domain.currentOwnerScriptPubKeyHex,
326
+ currentOwnerLocalIndex: options.moveOwnershipToTarget
327
+ ? options.target.localIndex
328
+ : domain.currentOwnerLocalIndex,
329
+ localAnchorIntent: options.localAnchorIntent,
330
+ canonicalChainStatus: options.canonicalChainStatus ?? domain.canonicalChainStatus,
331
+ currentCanonicalAnchorOutpoint: options.currentCanonicalAnchorOutpoint ?? domain.currentCanonicalAnchorOutpoint,
332
+ foundingMessageText: options.family.foundingMessageText ?? domain.foundingMessageText,
333
+ };
334
+ });
335
+ return {
336
+ ...upsertProactiveFamily(options.state, nextFamily),
337
+ identities,
338
+ domains,
339
+ };
340
+ }
341
+ function buildTx1Plan(options) {
342
+ const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
343
+ && isSpendableConfirmedUtxo(entry)));
344
+ const outputs = [
345
+ { data: Buffer.from(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData).toString("hex") },
346
+ { [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
347
+ ];
348
+ if (options.operation.sourceAnchorOutpoint === null) {
349
+ if (fundingUtxos.length === 0) {
350
+ throw new Error("wallet_anchor_sender_utxo_unavailable");
351
+ }
352
+ const senderInput = fundingUtxos[0];
353
+ return {
354
+ sender: options.operation.sourceSender,
355
+ changeAddress: options.state.funding.address,
356
+ inputs: fundingUtxos.map((entry, index) => index === 0
357
+ ? { txid: senderInput.txid, vout: senderInput.vout }
358
+ : { txid: entry.txid, vout: entry.vout }),
359
+ outputs,
360
+ changePosition: 2,
361
+ expectedOpReturnScriptHex: encodeOpReturnScript(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData),
362
+ expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
363
+ expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
364
+ expectedReplacementAnchorScriptHex: null,
365
+ expectedReplacementAnchorValueSats: null,
366
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
367
+ errorPrefix: "wallet_anchor_tx1",
368
+ };
369
+ }
370
+ const sourceAnchor = options.allUtxos.find((entry) => entry.txid === options.operation.sourceAnchorOutpoint?.txid
371
+ && entry.vout === options.operation.sourceAnchorOutpoint.vout
372
+ && entry.scriptPubKey === options.operation.sourceSender.scriptPubKeyHex
373
+ && isSpendableConfirmedUtxo(entry));
374
+ if (sourceAnchor === undefined) {
375
+ throw new Error("wallet_anchor_source_anchor_missing");
376
+ }
377
+ outputs.push({
378
+ [options.operation.sourceSender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)),
379
+ });
380
+ return {
381
+ sender: options.operation.sourceSender,
382
+ changeAddress: options.state.funding.address,
383
+ inputs: [
384
+ { txid: sourceAnchor.txid, vout: sourceAnchor.vout },
385
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
386
+ ],
387
+ outputs,
388
+ changePosition: 3,
389
+ expectedOpReturnScriptHex: encodeOpReturnScript(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData),
390
+ expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
391
+ expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
392
+ expectedReplacementAnchorScriptHex: options.operation.sourceSender.scriptPubKeyHex,
393
+ expectedReplacementAnchorValueSats: BigInt(options.state.anchorValueSats),
394
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
395
+ errorPrefix: "wallet_anchor_tx1",
396
+ };
397
+ }
398
+ function buildTx2Plan(options) {
399
+ const tx1Txid = options.family.tx1?.attemptedTxid;
400
+ if (tx1Txid === null || tx1Txid === undefined) {
401
+ throw new Error("wallet_anchor_tx1_missing");
402
+ }
403
+ const provisional = options.allUtxos.find((entry) => entry.txid === tx1Txid
404
+ && entry.vout === 1
405
+ && entry.scriptPubKey === options.operation.targetIdentity.scriptPubKeyHex
406
+ && entry.spendable !== false
407
+ && entry.safe !== false);
408
+ if (provisional === undefined) {
409
+ throw new Error("wallet_anchor_provisional_anchor_missing");
410
+ }
411
+ const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
412
+ && isSpendableConfirmedUtxo(entry)));
413
+ const foundingPayload = options.operation.foundingMessagePayloadHex === null
414
+ ? undefined
415
+ : Buffer.from(options.operation.foundingMessagePayloadHex, "hex");
416
+ const opReturnData = serializeDomainAnchor(options.operation.chainDomain.domainId, foundingPayload).opReturnData;
417
+ return {
418
+ sender: {
419
+ localIndex: options.operation.targetIdentity.localIndex,
420
+ scriptPubKeyHex: options.operation.targetIdentity.scriptPubKeyHex,
421
+ address: options.operation.targetIdentity.address,
422
+ },
423
+ changeAddress: options.state.funding.address,
424
+ inputs: [
425
+ { txid: provisional.txid, vout: provisional.vout },
426
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
427
+ ],
428
+ outputs: [
429
+ { data: Buffer.from(opReturnData).toString("hex") },
430
+ { [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
431
+ ],
432
+ changePosition: 2,
433
+ expectedOpReturnScriptHex: encodeOpReturnScript(opReturnData),
434
+ expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
435
+ expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
436
+ expectedReplacementAnchorScriptHex: null,
437
+ expectedReplacementAnchorValueSats: null,
438
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
439
+ errorPrefix: "wallet_anchor_tx2",
440
+ };
441
+ }
442
+ function validateTx1Draft(decoded, funded, plan) {
443
+ const inputs = decoded.tx.vin;
444
+ const outputs = decoded.tx.vout;
445
+ if (inputs.length === 0 || inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
446
+ throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
447
+ }
448
+ for (let index = 1; index < inputs.length; index += 1) {
449
+ if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
450
+ throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
451
+ }
452
+ }
453
+ if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
454
+ throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
455
+ }
456
+ if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
457
+ throw new Error(`${plan.errorPrefix}_provisional_anchor_output_mismatch`);
458
+ }
459
+ if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
460
+ throw new Error(`${plan.errorPrefix}_provisional_anchor_value_mismatch`);
461
+ }
462
+ const expectedWithoutChange = plan.expectedReplacementAnchorScriptHex === null ? 2 : 3;
463
+ if (plan.expectedReplacementAnchorScriptHex !== null) {
464
+ if (outputs[2]?.scriptPubKey?.hex !== plan.expectedReplacementAnchorScriptHex) {
465
+ throw new Error(`${plan.errorPrefix}_replacement_anchor_output_mismatch`);
466
+ }
467
+ if (valueToSats(outputs[2]?.value ?? 0) !== (plan.expectedReplacementAnchorValueSats ?? 0n)) {
468
+ throw new Error(`${plan.errorPrefix}_replacement_anchor_value_mismatch`);
469
+ }
470
+ }
471
+ if (funded.changepos === -1) {
472
+ if (outputs.length !== expectedWithoutChange) {
473
+ throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
474
+ }
475
+ return;
476
+ }
477
+ if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
478
+ throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
479
+ }
480
+ if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
481
+ throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
482
+ }
483
+ }
484
+ function validateTx2Draft(decoded, funded, plan) {
485
+ const inputs = decoded.tx.vin;
486
+ const outputs = decoded.tx.vout;
487
+ if (inputs.length === 0 || inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
488
+ throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
489
+ }
490
+ for (let index = 1; index < inputs.length; index += 1) {
491
+ if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
492
+ throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
493
+ }
494
+ }
495
+ if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
496
+ throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
497
+ }
498
+ if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
499
+ throw new Error(`${plan.errorPrefix}_canonical_anchor_output_mismatch`);
500
+ }
501
+ if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
502
+ throw new Error(`${plan.errorPrefix}_canonical_anchor_value_mismatch`);
503
+ }
504
+ const expectedWithoutChange = 2;
505
+ if (funded.changepos === -1) {
506
+ if (outputs.length !== expectedWithoutChange) {
507
+ throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
508
+ }
509
+ return;
510
+ }
511
+ if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
512
+ throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
513
+ }
514
+ if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
515
+ throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
516
+ }
517
+ }
518
+ async function buildTx1(options) {
519
+ return buildWalletMutationTransaction({
520
+ rpc: options.rpc,
521
+ walletName: options.walletName,
522
+ plan: options.plan,
523
+ validateFundedDraft: validateTx1Draft,
524
+ finalizeErrorCode: "wallet_anchor_tx1_finalize_failed",
525
+ mempoolRejectPrefix: "wallet_anchor_tx1_mempool_rejected",
526
+ });
527
+ }
528
+ async function buildTx2(options) {
529
+ return buildWalletMutationTransaction({
530
+ rpc: options.rpc,
531
+ walletName: options.walletName,
532
+ plan: options.plan,
533
+ validateFundedDraft: validateTx2Draft,
534
+ finalizeErrorCode: "wallet_anchor_tx2_finalize_failed",
535
+ mempoolRejectPrefix: "wallet_anchor_tx2_mempool_rejected",
536
+ builderOptions: {
537
+ includeUnsafe: true,
538
+ minConf: 0,
539
+ },
540
+ });
541
+ }
542
+ async function relockAnchorOutpoint(rpc, walletName, outpoint) {
543
+ if (outpoint === null) {
544
+ return;
545
+ }
546
+ await rpc.lockUnspent(walletName, false, [outpoint]).catch(() => undefined);
547
+ }
548
+ function resolveAcceptedFamilyStatus(options) {
549
+ const chainDomain = options.snapshot === null || options.family.domainName == null
550
+ ? null
551
+ : lookupDomain(options.snapshot.state, options.family.domainName);
552
+ if (chainDomain === null) {
553
+ return "live";
554
+ }
555
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
556
+ return chainDomain.anchored && ownerHex === options.target.scriptPubKeyHex
557
+ ? "confirmed"
558
+ : "live";
559
+ }
560
+ async function reconcileAnchorFamily(options) {
561
+ const chainDomain = lookupDomain(options.operation.readContext.snapshot.state, options.operation.chainDomain.name);
562
+ const targetScript = options.operation.targetIdentity.scriptPubKeyHex;
563
+ if (chainDomain !== null) {
564
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
565
+ if (chainDomain.anchored && ownerHex === targetScript) {
566
+ const nextState = updateAnchorFamilyState({
567
+ state: options.state,
568
+ family: options.family,
569
+ target: options.operation.targetIdentity,
570
+ status: "confirmed",
571
+ localAnchorIntent: "none",
572
+ currentStep: "tx2",
573
+ nowUnixMs: options.nowUnixMs,
574
+ tx1: options.family.tx1 == null ? undefined : { ...options.family.tx1, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
575
+ tx2: options.family.tx2 == null ? undefined : { ...options.family.tx2, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
576
+ moveOwnershipToTarget: true,
577
+ canonicalChainStatus: "anchored",
578
+ currentCanonicalAnchorOutpoint: options.family.tx2?.attemptedTxid == null
579
+ ? options.state.domains.find((domain) => domain.name === options.family.domainName)?.currentCanonicalAnchorOutpoint ?? null
580
+ : {
581
+ txid: options.family.tx2.attemptedTxid,
582
+ vout: 1,
583
+ valueSats: options.state.anchorValueSats,
584
+ },
585
+ });
586
+ await saveWalletStatePreservingUnlock({
587
+ state: {
588
+ ...nextState,
589
+ stateRevision: nextState.stateRevision + 1,
590
+ lastWrittenAtUnixMs: options.nowUnixMs,
591
+ },
592
+ provider: options.provider,
593
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
594
+ nowUnixMs: options.nowUnixMs,
595
+ paths: options.paths,
596
+ });
597
+ return {
598
+ state: nextState,
599
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
600
+ ...options.family,
601
+ status: "confirmed",
602
+ },
603
+ resolution: "confirmed",
604
+ };
605
+ }
606
+ if (ownerHex === targetScript && !chainDomain.anchored) {
607
+ const nextState = updateAnchorFamilyState({
608
+ state: options.state,
609
+ family: options.family,
610
+ target: options.operation.targetIdentity,
611
+ status: "repair-required",
612
+ localAnchorIntent: "repair-required",
613
+ currentStep: "tx2",
614
+ nowUnixMs: options.nowUnixMs,
615
+ listingCancelCommitted: true,
616
+ moveOwnershipToTarget: true,
617
+ });
618
+ await saveWalletStatePreservingUnlock({
619
+ state: {
620
+ ...nextState,
621
+ stateRevision: nextState.stateRevision + 1,
622
+ lastWrittenAtUnixMs: options.nowUnixMs,
623
+ },
624
+ provider: options.provider,
625
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
626
+ nowUnixMs: options.nowUnixMs,
627
+ paths: options.paths,
628
+ });
629
+ return {
630
+ state: nextState,
631
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
632
+ ...options.family,
633
+ status: "repair-required",
634
+ },
635
+ resolution: "repair-required",
636
+ };
637
+ }
638
+ }
639
+ const mempool = await options.rpc.getRawMempool().catch(() => []);
640
+ if (options.family.tx2?.attemptedTxid != null && mempool.includes(options.family.tx2.attemptedTxid)) {
641
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2.temporaryBuilderLockedOutpoints);
642
+ const nextState = updateAnchorFamilyState({
643
+ state: options.state,
644
+ family: options.family,
645
+ target: options.operation.targetIdentity,
646
+ status: "live",
647
+ localAnchorIntent: "tx2-live",
648
+ currentStep: "tx2",
649
+ nowUnixMs: options.nowUnixMs,
650
+ tx2: {
651
+ ...options.family.tx2,
652
+ status: "live",
653
+ temporaryBuilderLockedOutpoints: [],
654
+ },
655
+ listingCancelCommitted: true,
656
+ moveOwnershipToTarget: true,
657
+ currentCanonicalAnchorOutpoint: {
658
+ txid: options.family.tx2.attemptedTxid,
659
+ vout: 1,
660
+ valueSats: options.state.anchorValueSats,
661
+ },
662
+ });
663
+ await relockAnchorOutpoint(options.rpc, options.walletName, {
664
+ txid: options.family.tx2.attemptedTxid,
665
+ vout: 1,
666
+ });
667
+ await saveWalletStatePreservingUnlock({
668
+ state: {
669
+ ...nextState,
670
+ stateRevision: nextState.stateRevision + 1,
671
+ lastWrittenAtUnixMs: options.nowUnixMs,
672
+ },
673
+ provider: options.provider,
674
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
675
+ nowUnixMs: options.nowUnixMs,
676
+ paths: options.paths,
677
+ });
678
+ return {
679
+ state: nextState,
680
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
681
+ ...options.family,
682
+ status: "live",
683
+ },
684
+ resolution: "live",
685
+ };
686
+ }
687
+ if (options.family.tx1?.attemptedTxid != null && mempool.includes(options.family.tx1.attemptedTxid)) {
688
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx1.temporaryBuilderLockedOutpoints);
689
+ const nextState = updateAnchorFamilyState({
690
+ state: options.state,
691
+ family: options.family,
692
+ target: options.operation.targetIdentity,
693
+ status: "live",
694
+ localAnchorIntent: "tx1-live",
695
+ currentStep: "tx1",
696
+ nowUnixMs: options.nowUnixMs,
697
+ tx1: {
698
+ ...options.family.tx1,
699
+ status: "live",
700
+ temporaryBuilderLockedOutpoints: [],
701
+ },
702
+ listingCancelCommitted: options.operation.hadListing,
703
+ moveOwnershipToTarget: true,
704
+ });
705
+ if (options.operation.sourceAnchorOutpoint !== null) {
706
+ await relockAnchorOutpoint(options.rpc, options.walletName, {
707
+ txid: options.family.tx1.attemptedTxid,
708
+ vout: 2,
709
+ });
710
+ }
711
+ await saveWalletStatePreservingUnlock({
712
+ state: {
713
+ ...nextState,
714
+ stateRevision: nextState.stateRevision + 1,
715
+ lastWrittenAtUnixMs: options.nowUnixMs,
716
+ },
717
+ provider: options.provider,
718
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
719
+ nowUnixMs: options.nowUnixMs,
720
+ paths: options.paths,
721
+ });
722
+ return {
723
+ state: nextState,
724
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
725
+ ...options.family,
726
+ status: "live",
727
+ },
728
+ resolution: "ready-for-tx2",
729
+ };
730
+ }
731
+ if (options.family.currentStep === "tx2" || options.family.tx2?.attemptedTxid != null) {
732
+ const nextState = updateAnchorFamilyState({
733
+ state: options.state,
734
+ family: options.family,
735
+ target: options.operation.targetIdentity,
736
+ status: "repair-required",
737
+ localAnchorIntent: "repair-required",
738
+ currentStep: "tx2",
739
+ nowUnixMs: options.nowUnixMs,
740
+ listingCancelCommitted: true,
741
+ moveOwnershipToTarget: true,
742
+ });
743
+ await saveWalletStatePreservingUnlock({
744
+ state: {
745
+ ...nextState,
746
+ stateRevision: nextState.stateRevision + 1,
747
+ lastWrittenAtUnixMs: options.nowUnixMs,
748
+ },
749
+ provider: options.provider,
750
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
751
+ nowUnixMs: options.nowUnixMs,
752
+ paths: options.paths,
753
+ });
754
+ return {
755
+ state: nextState,
756
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
757
+ ...options.family,
758
+ status: "repair-required",
759
+ },
760
+ resolution: "repair-required",
761
+ };
762
+ }
763
+ if (ACTIVE_FAMILY_STATUSES.has(options.family.status)) {
764
+ const nextState = updateAnchorFamilyState({
765
+ state: options.state,
766
+ family: options.family,
767
+ target: options.operation.targetIdentity,
768
+ status: "canceled",
769
+ localAnchorIntent: "none",
770
+ currentStep: options.family.currentStep,
771
+ nowUnixMs: options.nowUnixMs,
772
+ tx1: options.family.tx1 == null ? undefined : {
773
+ ...options.family.tx1,
774
+ status: "canceled",
775
+ temporaryBuilderLockedOutpoints: [],
776
+ },
777
+ tx2: options.family.tx2 == null ? undefined : {
778
+ ...options.family.tx2,
779
+ status: "canceled",
780
+ temporaryBuilderLockedOutpoints: [],
781
+ },
782
+ });
783
+ await saveWalletStatePreservingUnlock({
784
+ state: {
785
+ ...nextState,
786
+ stateRevision: nextState.stateRevision + 1,
787
+ lastWrittenAtUnixMs: options.nowUnixMs,
788
+ },
789
+ provider: options.provider,
790
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
791
+ nowUnixMs: options.nowUnixMs,
792
+ paths: options.paths,
793
+ });
794
+ return {
795
+ state: nextState,
796
+ family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
797
+ ...options.family,
798
+ status: "canceled",
799
+ },
800
+ resolution: "not-seen",
801
+ };
802
+ }
803
+ return {
804
+ state: options.state,
805
+ family: options.family,
806
+ resolution: "continue",
807
+ };
808
+ }
809
+ function createBroadcastingTxRecord(built) {
810
+ return {
811
+ status: "broadcasting",
812
+ attemptedTxid: built.txid,
813
+ attemptedWtxid: built.wtxid,
814
+ temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
815
+ rawHex: built.rawHex,
816
+ };
817
+ }
818
+ async function saveState(state, provider, unlockUntilUnixMs, nowUnixMs, paths) {
819
+ const nextState = {
820
+ ...state,
821
+ stateRevision: state.stateRevision + 1,
822
+ lastWrittenAtUnixMs: nowUnixMs,
823
+ };
824
+ await saveWalletStatePreservingUnlock({
825
+ state: nextState,
826
+ provider,
827
+ unlockUntilUnixMs,
828
+ nowUnixMs,
829
+ paths,
830
+ });
831
+ return nextState;
832
+ }
833
+ async function submitTx2(options) {
834
+ let nextState = options.state;
835
+ let family = options.family;
836
+ const tx2Plan = buildTx2Plan({
837
+ state: nextState,
838
+ allUtxos: await options.rpc.listUnspent(options.walletName, 0),
839
+ operation: options.operation,
840
+ family,
841
+ });
842
+ const builtTx2 = await buildTx2({
843
+ rpc: options.rpc,
844
+ walletName: options.walletName,
845
+ plan: tx2Plan,
846
+ });
847
+ const broadcastingTx2 = createBroadcastingTxRecord(builtTx2);
848
+ family = {
849
+ ...family,
850
+ status: "broadcasting",
851
+ currentStep: "tx2",
852
+ tx2: broadcastingTx2,
853
+ };
854
+ nextState = updateAnchorFamilyState({
855
+ state: nextState,
856
+ family,
857
+ target: options.operation.targetIdentity,
858
+ status: "broadcasting",
859
+ localAnchorIntent: "tx1-live",
860
+ currentStep: "tx2",
861
+ nowUnixMs: options.nowUnixMs,
862
+ tx2: broadcastingTx2,
863
+ listingCancelCommitted: true,
864
+ moveOwnershipToTarget: true,
865
+ });
866
+ nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
867
+ ensureSameTipHeight(options.readContext, (await options.rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
868
+ try {
869
+ await options.rpc.sendRawTransaction(builtTx2.rawHex);
870
+ }
871
+ catch (error) {
872
+ if (!isAlreadyAcceptedError(error)) {
873
+ if (isBroadcastUnknownError(error)) {
874
+ family = {
875
+ ...family,
876
+ status: "broadcast-unknown",
877
+ tx2: {
878
+ ...broadcastingTx2,
879
+ status: "broadcast-unknown",
880
+ },
881
+ };
882
+ nextState = updateAnchorFamilyState({
883
+ state: nextState,
884
+ family,
885
+ target: options.operation.targetIdentity,
886
+ status: "broadcast-unknown",
887
+ localAnchorIntent: "tx1-live",
888
+ currentStep: "tx2",
889
+ nowUnixMs: options.nowUnixMs,
890
+ tx2: family.tx2,
891
+ listingCancelCommitted: true,
892
+ moveOwnershipToTarget: true,
893
+ });
894
+ await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
895
+ throw new Error("wallet_anchor_tx2_broadcast_unknown");
896
+ }
897
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
898
+ family = {
899
+ ...family,
900
+ status: "repair-required",
901
+ tx2: {
902
+ ...broadcastingTx2,
903
+ status: "repair-required",
904
+ temporaryBuilderLockedOutpoints: [],
905
+ },
906
+ };
907
+ nextState = updateAnchorFamilyState({
908
+ state: nextState,
909
+ family,
910
+ target: options.operation.targetIdentity,
911
+ status: "repair-required",
912
+ localAnchorIntent: "repair-required",
913
+ currentStep: "tx2",
914
+ nowUnixMs: options.nowUnixMs,
915
+ tx2: family.tx2,
916
+ listingCancelCommitted: true,
917
+ moveOwnershipToTarget: true,
918
+ });
919
+ await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
920
+ throw error;
921
+ }
922
+ }
923
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
924
+ const finalStatus = resolveAcceptedFamilyStatus({
925
+ snapshot: options.readContext.snapshot,
926
+ family,
927
+ target: options.operation.targetIdentity,
928
+ });
929
+ family = {
930
+ ...family,
931
+ status: finalStatus,
932
+ currentStep: "tx2",
933
+ tx2: {
934
+ ...broadcastingTx2,
935
+ status: finalStatus,
936
+ temporaryBuilderLockedOutpoints: [],
937
+ },
938
+ };
939
+ nextState = updateAnchorFamilyState({
940
+ state: nextState,
941
+ family,
942
+ target: options.operation.targetIdentity,
943
+ status: finalStatus,
944
+ localAnchorIntent: finalStatus === "confirmed" ? "none" : "tx2-live",
945
+ currentStep: "tx2",
946
+ nowUnixMs: options.nowUnixMs,
947
+ tx2: family.tx2,
948
+ listingCancelCommitted: true,
949
+ moveOwnershipToTarget: true,
950
+ canonicalChainStatus: finalStatus === "confirmed" ? "anchored" : undefined,
951
+ currentCanonicalAnchorOutpoint: {
952
+ txid: builtTx2.txid,
953
+ vout: 1,
954
+ valueSats: nextState.anchorValueSats,
955
+ },
956
+ });
957
+ nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
958
+ await relockAnchorOutpoint(options.rpc, options.walletName, {
959
+ txid: builtTx2.txid,
960
+ vout: 1,
961
+ });
962
+ return {
963
+ domainName: options.operation.chainDomain.name,
964
+ txid: builtTx2.txid,
965
+ tx1Txid: family.tx1?.attemptedTxid ?? "unknown",
966
+ tx2Txid: builtTx2.txid,
967
+ dedicatedIndex: options.operation.targetIdentity.localIndex,
968
+ status: finalStatus,
969
+ reusedExisting: false,
970
+ };
971
+ }
972
+ function ensureSameTipHeight(context, bestHeight, errorCode) {
973
+ if (context.snapshot?.tip?.height !== bestHeight) {
974
+ throw new Error(errorCode);
975
+ }
976
+ }
977
+ export async function anchorDomain(options) {
978
+ if (!options.prompter.isInteractive) {
979
+ throw new Error("wallet_anchor_requires_tty");
980
+ }
981
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
982
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
983
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
984
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
985
+ purpose: "wallet-anchor",
986
+ walletRootId: null,
987
+ });
988
+ const normalizedDomainName = normalizeDomainName(options.domainName);
989
+ try {
990
+ const miningPreemption = await pauseMiningForWalletMutation({
991
+ paths,
992
+ reason: "wallet-anchor",
993
+ });
994
+ const message = await encodeFoundingMessage(options.foundingMessageText ?? null);
995
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
996
+ dataDir: options.dataDir,
997
+ databasePath: options.databasePath,
998
+ secretProvider: provider,
999
+ paths,
1000
+ });
1001
+ try {
1002
+ let operation = resolveAnchorOperation(readContext, normalizedDomainName, message.text, message.payloadHex);
1003
+ const initialFamily = createDraftAnchorFamily(operation, nowUnixMs);
1004
+ const existingFamily = findAnchorFamilyByIntent(operation.state, initialFamily.intentFingerprintHex);
1005
+ const conflictingFamily = findActiveAnchorFamilyByDomain(operation.state, normalizedDomainName);
1006
+ if (existingFamily === null && conflictingFamily !== null) {
1007
+ throw new Error("wallet_anchor_prior_family_unresolved");
1008
+ }
1009
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1010
+ dataDir: options.dataDir,
1011
+ chain: "main",
1012
+ startHeight: 0,
1013
+ walletRootId: operation.state.walletRootId,
1014
+ });
1015
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1016
+ const walletName = operation.state.managedCoreWallet.walletName;
1017
+ let resumedFamily = null;
1018
+ let resumedExisting = false;
1019
+ let workingState = operation.state;
1020
+ if (existingFamily !== null) {
1021
+ const existingReservedIndex = existingFamily.reservedDedicatedIndex ?? operation.targetIdentity.localIndex;
1022
+ const existingTargetIdentity = {
1023
+ ...deriveWalletIdentityMaterial(operation.state.keys.accountXprv, existingReservedIndex),
1024
+ localIndex: existingReservedIndex,
1025
+ };
1026
+ const reconciled = await reconcileAnchorFamily({
1027
+ state: operation.state,
1028
+ family: existingFamily,
1029
+ operation: {
1030
+ ...operation,
1031
+ targetIdentity: existingTargetIdentity,
1032
+ },
1033
+ provider,
1034
+ nowUnixMs,
1035
+ paths,
1036
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1037
+ rpc,
1038
+ walletName,
1039
+ });
1040
+ workingState = reconciled.state;
1041
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
1042
+ return {
1043
+ domainName: normalizedDomainName,
1044
+ txid: reconciled.family.tx2?.attemptedTxid ?? reconciled.family.tx1?.attemptedTxid ?? "unknown",
1045
+ tx1Txid: reconciled.family.tx1?.attemptedTxid ?? "unknown",
1046
+ tx2Txid: reconciled.family.tx2?.attemptedTxid ?? "unknown",
1047
+ dedicatedIndex: reconciled.family.reservedDedicatedIndex ?? existingTargetIdentity.localIndex,
1048
+ status: reconciled.resolution,
1049
+ reusedExisting: true,
1050
+ };
1051
+ }
1052
+ if (reconciled.resolution === "repair-required") {
1053
+ throw new Error("wallet_anchor_repair_required");
1054
+ }
1055
+ if (reconciled.resolution === "ready-for-tx2") {
1056
+ operation = {
1057
+ ...operation,
1058
+ targetIdentity: existingTargetIdentity,
1059
+ };
1060
+ resumedFamily = reconciled.family;
1061
+ resumedExisting = true;
1062
+ }
1063
+ }
1064
+ let nextState = workingState;
1065
+ let family;
1066
+ if (resumedFamily !== null) {
1067
+ family = resumedFamily;
1068
+ }
1069
+ else {
1070
+ await confirmAnchor(options.prompter, operation);
1071
+ nextState = reserveAnchorFamilyState(nextState, initialFamily, operation.targetIdentity, operation.foundingMessageText);
1072
+ nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
1073
+ const tx1Plan = buildTx1Plan({
1074
+ state: nextState,
1075
+ allUtxos: await rpc.listUnspent(walletName, 1),
1076
+ operation,
1077
+ });
1078
+ const builtTx1 = await buildTx1({
1079
+ rpc,
1080
+ walletName,
1081
+ plan: tx1Plan,
1082
+ });
1083
+ const broadcastingTx1 = createBroadcastingTxRecord(builtTx1);
1084
+ family = {
1085
+ ...(findAnchorFamilyByIntent(nextState, initialFamily.intentFingerprintHex) ?? initialFamily),
1086
+ status: "broadcasting",
1087
+ currentStep: "tx1",
1088
+ lastUpdatedAtUnixMs: nowUnixMs,
1089
+ tx1: broadcastingTx1,
1090
+ };
1091
+ nextState = updateAnchorFamilyState({
1092
+ state: nextState,
1093
+ family,
1094
+ target: operation.targetIdentity,
1095
+ status: "broadcasting",
1096
+ localAnchorIntent: "reserved",
1097
+ currentStep: "tx1",
1098
+ nowUnixMs,
1099
+ tx1: broadcastingTx1,
1100
+ });
1101
+ nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
1102
+ ensureSameTipHeight(readContext, (await rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
1103
+ try {
1104
+ await rpc.sendRawTransaction(builtTx1.rawHex);
1105
+ }
1106
+ catch (error) {
1107
+ if (!isAlreadyAcceptedError(error)) {
1108
+ if (isBroadcastUnknownError(error)) {
1109
+ family = {
1110
+ ...family,
1111
+ status: "broadcast-unknown",
1112
+ tx1: {
1113
+ ...broadcastingTx1,
1114
+ status: "broadcast-unknown",
1115
+ },
1116
+ };
1117
+ nextState = updateAnchorFamilyState({
1118
+ state: nextState,
1119
+ family,
1120
+ target: operation.targetIdentity,
1121
+ status: "broadcast-unknown",
1122
+ localAnchorIntent: "reserved",
1123
+ currentStep: "tx1",
1124
+ nowUnixMs,
1125
+ tx1: family.tx1,
1126
+ });
1127
+ await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
1128
+ throw new Error("wallet_anchor_tx1_broadcast_unknown");
1129
+ }
1130
+ await unlockTemporaryBuilderLocks(rpc, walletName, builtTx1.temporaryBuilderLockedOutpoints);
1131
+ family = {
1132
+ ...family,
1133
+ status: "canceled",
1134
+ tx1: {
1135
+ ...broadcastingTx1,
1136
+ status: "canceled",
1137
+ temporaryBuilderLockedOutpoints: [],
1138
+ },
1139
+ };
1140
+ nextState = updateAnchorFamilyState({
1141
+ state: nextState,
1142
+ family,
1143
+ target: operation.targetIdentity,
1144
+ status: "canceled",
1145
+ localAnchorIntent: "none",
1146
+ currentStep: "tx1",
1147
+ nowUnixMs,
1148
+ tx1: family.tx1,
1149
+ });
1150
+ await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
1151
+ throw error;
1152
+ }
1153
+ }
1154
+ await unlockTemporaryBuilderLocks(rpc, walletName, builtTx1.temporaryBuilderLockedOutpoints);
1155
+ family = {
1156
+ ...family,
1157
+ status: "live",
1158
+ currentStep: "tx1",
1159
+ tx1: {
1160
+ ...broadcastingTx1,
1161
+ status: "live",
1162
+ temporaryBuilderLockedOutpoints: [],
1163
+ },
1164
+ };
1165
+ nextState = updateAnchorFamilyState({
1166
+ state: nextState,
1167
+ family,
1168
+ target: operation.targetIdentity,
1169
+ status: "live",
1170
+ localAnchorIntent: "tx1-live",
1171
+ currentStep: "tx1",
1172
+ nowUnixMs,
1173
+ tx1: family.tx1,
1174
+ listingCancelCommitted: operation.hadListing,
1175
+ moveOwnershipToTarget: true,
1176
+ });
1177
+ nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
1178
+ if (operation.sourceAnchorOutpoint !== null) {
1179
+ await relockAnchorOutpoint(rpc, walletName, {
1180
+ txid: builtTx1.txid,
1181
+ vout: 2,
1182
+ });
1183
+ }
1184
+ }
1185
+ const result = await submitTx2({
1186
+ state: nextState,
1187
+ family,
1188
+ operation,
1189
+ readContext: operation.readContext,
1190
+ provider,
1191
+ rpc,
1192
+ walletName,
1193
+ nowUnixMs,
1194
+ paths,
1195
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1196
+ });
1197
+ return {
1198
+ ...result,
1199
+ reusedExisting: resumedExisting,
1200
+ };
1201
+ }
1202
+ finally {
1203
+ await readContext.close();
1204
+ await miningPreemption.release();
1205
+ }
1206
+ }
1207
+ finally {
1208
+ await controlLock.release();
1209
+ }
1210
+ }