@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,1853 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve as resolvePath } from "node:path";
4
+ import { getBalance, lookupDomain, } from "@cogcoin/indexer/queries";
5
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
6
+ import { createRpcClient } from "../../bitcoind/node.js";
7
+ import { acquireFileLock } from "../fs/lock.js";
8
+ import { resolveWalletRuntimePathsForTesting, } from "../runtime.js";
9
+ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
10
+ import { FIELD_FORMAT_BYTES, serializeDataUpdate, serializeFieldReg, } from "../cogop/index.js";
11
+ import { validateFieldName } from "../cogop/validate-name.js";
12
+ import { findDomainField, openWalletReadContext, } from "../read/index.js";
13
+ import { assertWalletMutationContextReady, buildWalletMutationTransaction, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
14
+ import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
15
+ import { getCanonicalIdentitySelector } from "./identity-selector.js";
16
+ import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
17
+ function createResolvedFieldSenderSummary(sender, selector) {
18
+ return {
19
+ selector,
20
+ localIndex: sender.localIndex,
21
+ scriptPubKeyHex: sender.scriptPubKeyHex,
22
+ address: sender.address,
23
+ };
24
+ }
25
+ function createResolvedFieldValueSummary(format, value) {
26
+ return {
27
+ format,
28
+ byteLength: typeof value === "string" ? value.length / 2 : value.length,
29
+ };
30
+ }
31
+ function createResolvedFieldSummary(options) {
32
+ if (options.kind === "field-create") {
33
+ if (options.family) {
34
+ return {
35
+ sender: createResolvedFieldSenderSummary(options.sender, options.senderSelector),
36
+ path: "field-reg-plus-data-update-family",
37
+ value: options.value,
38
+ effect: {
39
+ kind: "create-and-initialize-field",
40
+ tx1BurnCogtoshi: "100",
41
+ tx2AdditionalBurnCogtoshi: "1",
42
+ },
43
+ };
44
+ }
45
+ return {
46
+ sender: createResolvedFieldSenderSummary(options.sender, options.senderSelector),
47
+ path: "standalone-field-reg",
48
+ value: null,
49
+ effect: {
50
+ kind: "create-empty-field",
51
+ burnCogtoshi: "100",
52
+ },
53
+ };
54
+ }
55
+ if (options.kind === "field-set") {
56
+ return {
57
+ sender: createResolvedFieldSenderSummary(options.sender, options.senderSelector),
58
+ path: "standalone-data-update",
59
+ value: options.value,
60
+ effect: {
61
+ kind: "write-field-value",
62
+ burnCogtoshi: "1",
63
+ },
64
+ };
65
+ }
66
+ return {
67
+ sender: createResolvedFieldSenderSummary(options.sender, options.senderSelector),
68
+ path: "standalone-data-clear",
69
+ value: null,
70
+ effect: {
71
+ kind: "clear-field-value",
72
+ burnCogtoshi: "0",
73
+ },
74
+ };
75
+ }
76
+ function createResolvedFieldValueFromStoredData(kind, format, valueHex) {
77
+ if (kind === "field-clear" || format === null || format === undefined || valueHex === null || valueHex === undefined) {
78
+ return null;
79
+ }
80
+ return createResolvedFieldValueSummary(format, valueHex);
81
+ }
82
+ function describeFieldEffect(effect) {
83
+ switch (effect.kind) {
84
+ case "create-empty-field":
85
+ return `burn ${effect.burnCogtoshi} cogtoshi to create an empty field`;
86
+ case "create-and-initialize-field":
87
+ return `burn ${effect.tx1BurnCogtoshi} cogtoshi in Tx1 and ${effect.tx2AdditionalBurnCogtoshi} additional cogtoshi in Tx2`;
88
+ case "write-field-value":
89
+ return `burn ${effect.burnCogtoshi} cogtoshi to write the field value`;
90
+ case "clear-field-value":
91
+ return "clear the field value with no additional COG burn";
92
+ }
93
+ }
94
+ function normalizeDomainName(domainName) {
95
+ const normalized = domainName.trim().toLowerCase();
96
+ if (normalized.length === 0) {
97
+ throw new Error("wallet_field_missing_domain");
98
+ }
99
+ return normalized;
100
+ }
101
+ function normalizeFieldName(fieldName) {
102
+ const normalized = fieldName.trim().toLowerCase();
103
+ if (normalized.length === 0) {
104
+ throw new Error("wallet_field_missing_field_name");
105
+ }
106
+ validateFieldName(normalized);
107
+ return normalized;
108
+ }
109
+ function encodeOpReturnScript(payload) {
110
+ if (payload.length <= 75) {
111
+ return Buffer.concat([
112
+ Buffer.from([0x6a, payload.length]),
113
+ Buffer.from(payload),
114
+ ]).toString("hex");
115
+ }
116
+ return Buffer.concat([
117
+ Buffer.from([0x6a, 0x4c, payload.length]),
118
+ Buffer.from(payload),
119
+ ]).toString("hex");
120
+ }
121
+ function satsToBtcNumber(value) {
122
+ return Number(value) / 100_000_000;
123
+ }
124
+ function valueToSats(value) {
125
+ const text = typeof value === "number" ? value.toFixed(8) : value;
126
+ const match = /^(-?)(\d+)(?:\.(\d{0,8}))?$/.exec(text.trim());
127
+ if (match == null) {
128
+ throw new Error(`wallet_field_invalid_amount_${text}`);
129
+ }
130
+ const sign = match[1] === "-" ? -1n : 1n;
131
+ const whole = BigInt(match[2] ?? "0");
132
+ const fraction = BigInt((match[3] ?? "").padEnd(8, "0"));
133
+ return sign * ((whole * 100000000n) + fraction);
134
+ }
135
+ function createIntentFingerprint(parts) {
136
+ return createHash("sha256")
137
+ .update(parts.map((part) => String(part)).join("\n"))
138
+ .digest("hex");
139
+ }
140
+ function hex(value) {
141
+ if (value === null || value === undefined) {
142
+ return null;
143
+ }
144
+ return Buffer.from(value).toString("hex");
145
+ }
146
+ function isActiveMutationStatus(status) {
147
+ return status === "draft"
148
+ || status === "broadcasting"
149
+ || status === "broadcast-unknown"
150
+ || status === "live"
151
+ || status === "repair-required";
152
+ }
153
+ function isActiveFamilyStatus(status) {
154
+ return status === "draft"
155
+ || status === "broadcasting"
156
+ || status === "broadcast-unknown"
157
+ || status === "live"
158
+ || status === "repair-required";
159
+ }
160
+ function createFamilyTransactionRecord() {
161
+ return {
162
+ status: "draft",
163
+ attemptedTxid: null,
164
+ attemptedWtxid: null,
165
+ temporaryBuilderLockedOutpoints: [],
166
+ rawHex: null,
167
+ };
168
+ }
169
+ function upsertProactiveFamily(state, family) {
170
+ const families = state.proactiveFamilies.slice();
171
+ const existingIndex = families.findIndex((entry) => entry.familyId === family.familyId);
172
+ if (existingIndex >= 0) {
173
+ families[existingIndex] = family;
174
+ }
175
+ else {
176
+ families.push(family);
177
+ }
178
+ return {
179
+ ...state,
180
+ proactiveFamilies: families,
181
+ };
182
+ }
183
+ function findFieldFamilyByIntent(state, intentFingerprintHex) {
184
+ return state.proactiveFamilies.find((family) => family.type === "field" && family.intentFingerprintHex === intentFingerprintHex) ?? null;
185
+ }
186
+ function findActiveFieldFamilyByDomain(state, domainName) {
187
+ return state.proactiveFamilies.find((family) => family.type === "field"
188
+ && family.domainName === domainName
189
+ && isActiveFamilyStatus(family.status)) ?? null;
190
+ }
191
+ function findActiveFieldCreateMutationByDomain(state, domainName, intentFingerprintHex) {
192
+ return (state.pendingMutations ?? []).find((mutation) => mutation.kind === "field-create"
193
+ && mutation.domainName === domainName
194
+ && mutation.intentFingerprintHex !== intentFingerprintHex
195
+ && isActiveMutationStatus(mutation.status)) ?? null;
196
+ }
197
+ function resolveAnchorOutpointForSender(state, sender, errorPrefix) {
198
+ const anchoredDomain = state.domains.find((domain) => domain.currentOwnerLocalIndex === sender.index
199
+ && domain.canonicalChainStatus === "anchored") ?? null;
200
+ if (anchoredDomain?.currentCanonicalAnchorOutpoint === null || anchoredDomain === null) {
201
+ throw new Error(`${errorPrefix}_anchor_outpoint_unavailable`);
202
+ }
203
+ return {
204
+ txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
205
+ vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
206
+ };
207
+ }
208
+ function resolveAnchoredFieldOperation(context, domainName, errorPrefix) {
209
+ assertWalletMutationContextReady(context, errorPrefix);
210
+ const chainDomain = lookupDomain(context.snapshot.state, domainName);
211
+ if (chainDomain === null) {
212
+ throw new Error(`${errorPrefix}_domain_not_found`);
213
+ }
214
+ if (!chainDomain.anchored) {
215
+ throw new Error(`${errorPrefix}_domain_not_anchored`);
216
+ }
217
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
218
+ const ownerIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === ownerHex) ?? null;
219
+ if (ownerIdentity === null || ownerIdentity.address === null) {
220
+ throw new Error(`${errorPrefix}_owner_not_locally_controlled`);
221
+ }
222
+ if (ownerIdentity.readOnly) {
223
+ throw new Error(`${errorPrefix}_owner_read_only`);
224
+ }
225
+ return {
226
+ readContext: context,
227
+ state: context.localState.state,
228
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
229
+ sender: {
230
+ localIndex: ownerIdentity.index,
231
+ scriptPubKeyHex: ownerIdentity.scriptPubKeyHex,
232
+ address: ownerIdentity.address,
233
+ },
234
+ senderSelector: getCanonicalIdentitySelector(ownerIdentity),
235
+ anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, ownerIdentity, errorPrefix),
236
+ chainDomain,
237
+ };
238
+ }
239
+ function buildAnchoredFieldPlan(options) {
240
+ const fundingUtxos = options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
241
+ && entry.confirmations >= 1
242
+ && entry.spendable !== false
243
+ && entry.safe !== false);
244
+ const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint.txid
245
+ && entry.vout === options.anchorOutpoint.vout
246
+ && entry.scriptPubKey === options.sender.scriptPubKeyHex
247
+ && entry.confirmations >= 1
248
+ && entry.spendable !== false
249
+ && entry.safe !== false);
250
+ if (anchorUtxo === undefined) {
251
+ throw new Error(`${options.errorPrefix}_anchor_utxo_missing`);
252
+ }
253
+ return {
254
+ sender: options.sender,
255
+ changeAddress: options.state.funding.address,
256
+ inputs: [
257
+ { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
258
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
259
+ ],
260
+ outputs: [
261
+ { data: Buffer.from(options.opReturnData).toString("hex") },
262
+ { [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
263
+ ],
264
+ changePosition: 2,
265
+ expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
266
+ expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
267
+ expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
268
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
269
+ errorPrefix: options.errorPrefix,
270
+ };
271
+ }
272
+ function buildFieldFamilyTx2Plan(options) {
273
+ const fundingUtxos = options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
274
+ && entry.confirmations >= 1
275
+ && entry.spendable !== false
276
+ && entry.safe !== false);
277
+ return {
278
+ sender: options.sender,
279
+ changeAddress: options.state.funding.address,
280
+ inputs: [
281
+ { txid: options.tx1Txid, vout: 1 },
282
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
283
+ ],
284
+ outputs: [
285
+ { data: Buffer.from(options.opReturnData).toString("hex") },
286
+ { [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
287
+ ],
288
+ changePosition: 2,
289
+ expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
290
+ expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
291
+ expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
292
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
293
+ errorPrefix: "wallet_field_create_tx2",
294
+ };
295
+ }
296
+ function validateFieldDraft(decoded, funded, plan) {
297
+ const inputs = decoded.tx.vin;
298
+ const outputs = decoded.tx.vout;
299
+ if (inputs.length === 0) {
300
+ throw new Error(`${plan.errorPrefix}_missing_sender_input`);
301
+ }
302
+ if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
303
+ throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
304
+ }
305
+ for (let index = 1; index < inputs.length; index += 1) {
306
+ if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
307
+ throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
308
+ }
309
+ }
310
+ if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
311
+ throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
312
+ }
313
+ if (outputs[1]?.scriptPubKey?.hex !== plan.expectedAnchorScriptHex) {
314
+ throw new Error(`${plan.errorPrefix}_anchor_output_mismatch`);
315
+ }
316
+ if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedAnchorValueSats) {
317
+ throw new Error(`${plan.errorPrefix}_anchor_value_mismatch`);
318
+ }
319
+ if (funded.changepos === -1) {
320
+ if (outputs.length !== 2) {
321
+ throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
322
+ }
323
+ return;
324
+ }
325
+ if (funded.changepos !== plan.changePosition || outputs.length !== 3) {
326
+ throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
327
+ }
328
+ if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
329
+ throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
330
+ }
331
+ }
332
+ async function buildFieldTransaction(options) {
333
+ return buildWalletMutationTransaction({
334
+ rpc: options.rpc,
335
+ walletName: options.walletName,
336
+ plan: options.plan,
337
+ validateFundedDraft: validateFieldDraft,
338
+ finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
339
+ mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
340
+ builderOptions: options.builderOptions,
341
+ });
342
+ }
343
+ async function saveUpdatedState(options) {
344
+ const nextState = {
345
+ ...options.state,
346
+ stateRevision: options.state.stateRevision + 1,
347
+ lastWrittenAtUnixMs: options.nowUnixMs,
348
+ };
349
+ await saveWalletStatePreservingUnlock({
350
+ state: nextState,
351
+ provider: options.provider,
352
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
353
+ nowUnixMs: options.nowUnixMs,
354
+ paths: options.paths,
355
+ });
356
+ return nextState;
357
+ }
358
+ function createStandaloneFieldMutation(options) {
359
+ if (options.existing !== null && options.existing !== undefined) {
360
+ return {
361
+ ...options.existing,
362
+ kind: options.kind,
363
+ domainName: options.domainName,
364
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
365
+ senderLocalIndex: options.sender.localIndex,
366
+ fieldName: options.fieldName,
367
+ fieldId: options.fieldId ?? null,
368
+ fieldPermanent: options.fieldPermanent ?? null,
369
+ fieldFormat: options.fieldFormat ?? null,
370
+ fieldValueHex: options.fieldValueHex ?? null,
371
+ status: "draft",
372
+ lastUpdatedAtUnixMs: options.nowUnixMs,
373
+ attemptedTxid: null,
374
+ attemptedWtxid: null,
375
+ temporaryBuilderLockedOutpoints: [],
376
+ };
377
+ }
378
+ return {
379
+ mutationId: randomBytes(12).toString("hex"),
380
+ kind: options.kind,
381
+ domainName: options.domainName,
382
+ parentDomainName: null,
383
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
384
+ senderLocalIndex: options.sender.localIndex,
385
+ fieldName: options.fieldName,
386
+ fieldId: options.fieldId ?? null,
387
+ fieldPermanent: options.fieldPermanent ?? null,
388
+ fieldFormat: options.fieldFormat ?? null,
389
+ fieldValueHex: options.fieldValueHex ?? null,
390
+ intentFingerprintHex: options.intentFingerprintHex,
391
+ status: "draft",
392
+ createdAtUnixMs: options.nowUnixMs,
393
+ lastUpdatedAtUnixMs: options.nowUnixMs,
394
+ attemptedTxid: null,
395
+ attemptedWtxid: null,
396
+ temporaryBuilderLockedOutpoints: [],
397
+ };
398
+ }
399
+ function createFieldFamilyRecord(options) {
400
+ if (options.existing !== null && options.existing !== undefined) {
401
+ return {
402
+ ...options.existing,
403
+ type: "field",
404
+ status: "draft",
405
+ domainName: options.domainName,
406
+ domainId: options.domainId,
407
+ sourceSenderLocalIndex: options.sender.localIndex,
408
+ sourceSenderScriptPubKeyHex: options.sender.scriptPubKeyHex,
409
+ fieldName: options.fieldName,
410
+ expectedFieldId: options.expectedFieldId,
411
+ fieldPermanent: options.permanence,
412
+ fieldFormat: options.format,
413
+ fieldValueHex: options.valueHex,
414
+ currentStep: "tx1",
415
+ lastUpdatedAtUnixMs: options.nowUnixMs,
416
+ tx1: createFamilyTransactionRecord(),
417
+ tx2: createFamilyTransactionRecord(),
418
+ };
419
+ }
420
+ return {
421
+ familyId: randomBytes(12).toString("hex"),
422
+ type: "field",
423
+ status: "draft",
424
+ intentFingerprintHex: options.intentFingerprintHex,
425
+ createdAtUnixMs: options.nowUnixMs,
426
+ lastUpdatedAtUnixMs: options.nowUnixMs,
427
+ domainName: options.domainName,
428
+ domainId: options.domainId,
429
+ sourceSenderLocalIndex: options.sender.localIndex,
430
+ sourceSenderScriptPubKeyHex: options.sender.scriptPubKeyHex,
431
+ fieldName: options.fieldName,
432
+ expectedFieldId: options.expectedFieldId,
433
+ fieldPermanent: options.permanence,
434
+ fieldFormat: options.format,
435
+ fieldValueHex: options.valueHex,
436
+ currentStep: "tx1",
437
+ tx1: createFamilyTransactionRecord(),
438
+ tx2: createFamilyTransactionRecord(),
439
+ };
440
+ }
441
+ function updateFieldFamilyState(options) {
442
+ return upsertProactiveFamily(options.state, {
443
+ ...options.family,
444
+ status: options.status,
445
+ currentStep: options.currentStep,
446
+ lastUpdatedAtUnixMs: options.nowUnixMs,
447
+ tx1: options.tx1 ?? options.family.tx1 ?? createFamilyTransactionRecord(),
448
+ tx2: options.tx2 ?? options.family.tx2 ?? createFamilyTransactionRecord(),
449
+ });
450
+ }
451
+ function getObservedFieldState(context, domainName, fieldName) {
452
+ if (context.snapshot === null) {
453
+ return null;
454
+ }
455
+ return findDomainField(context, domainName, fieldName);
456
+ }
457
+ function standaloneMutationConfirmedOnChain(mutation, context) {
458
+ const observed = mutation.fieldName == null
459
+ ? null
460
+ : getObservedFieldState(context, mutation.domainName, mutation.fieldName);
461
+ const chainDomain = context.snapshot === null ? null : lookupDomain(context.snapshot.state, mutation.domainName);
462
+ if (chainDomain === null || !chainDomain.anchored) {
463
+ return false;
464
+ }
465
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
466
+ if (ownerHex !== mutation.senderScriptPubKeyHex) {
467
+ return false;
468
+ }
469
+ if (mutation.kind === "field-create") {
470
+ return observed !== null
471
+ && (mutation.fieldPermanent == null || observed.permanent === mutation.fieldPermanent);
472
+ }
473
+ if (mutation.kind === "field-clear") {
474
+ return observed !== null && !observed.hasValue;
475
+ }
476
+ return observed !== null
477
+ && observed.hasValue
478
+ && observed.format === (mutation.fieldFormat ?? null)
479
+ && observed.rawValueHex === (mutation.fieldValueHex ?? null);
480
+ }
481
+ function standaloneMutationNeedsRepair(mutation, context) {
482
+ if (context.snapshot === null) {
483
+ return false;
484
+ }
485
+ const chainDomain = lookupDomain(context.snapshot.state, mutation.domainName);
486
+ if (chainDomain === null || !chainDomain.anchored) {
487
+ return true;
488
+ }
489
+ const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
490
+ if (ownerHex !== mutation.senderScriptPubKeyHex) {
491
+ return true;
492
+ }
493
+ if (mutation.fieldName == null) {
494
+ return false;
495
+ }
496
+ const observed = getObservedFieldState(context, mutation.domainName, mutation.fieldName);
497
+ if (mutation.kind === "field-create") {
498
+ return observed !== null
499
+ && mutation.fieldPermanent !== null
500
+ && observed.permanent !== mutation.fieldPermanent;
501
+ }
502
+ if (mutation.kind === "field-set") {
503
+ return observed !== null
504
+ && observed.hasValue
505
+ && ((mutation.fieldFormat ?? null) !== observed.format || (mutation.fieldValueHex ?? null) !== observed.rawValueHex);
506
+ }
507
+ return false;
508
+ }
509
+ async function reconcilePendingFieldMutation(options) {
510
+ if (options.mutation.status === "confirmed" || options.mutation.status === "live") {
511
+ return {
512
+ state: options.state,
513
+ mutation: options.mutation,
514
+ resolution: options.mutation.status,
515
+ };
516
+ }
517
+ if (options.mutation.status === "repair-required") {
518
+ return {
519
+ state: options.state,
520
+ mutation: options.mutation,
521
+ resolution: "repair-required",
522
+ };
523
+ }
524
+ if (standaloneMutationConfirmedOnChain(options.mutation, options.context)) {
525
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
526
+ const confirmed = updateMutationRecord(options.mutation, "confirmed", options.nowUnixMs, {
527
+ temporaryBuilderLockedOutpoints: [],
528
+ });
529
+ let nextState = upsertPendingMutation(options.state, confirmed);
530
+ nextState = await saveUpdatedState({
531
+ state: nextState,
532
+ provider: options.provider,
533
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
534
+ nowUnixMs: options.nowUnixMs,
535
+ paths: options.paths,
536
+ });
537
+ return { state: nextState, mutation: confirmed, resolution: "confirmed" };
538
+ }
539
+ if (standaloneMutationNeedsRepair(options.mutation, options.context)) {
540
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
541
+ const repair = updateMutationRecord(options.mutation, "repair-required", options.nowUnixMs, {
542
+ temporaryBuilderLockedOutpoints: [],
543
+ });
544
+ let nextState = upsertPendingMutation(options.state, repair);
545
+ nextState = await saveUpdatedState({
546
+ state: nextState,
547
+ provider: options.provider,
548
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
549
+ nowUnixMs: options.nowUnixMs,
550
+ paths: options.paths,
551
+ });
552
+ return { state: nextState, mutation: repair, resolution: "repair-required" };
553
+ }
554
+ const known = options.mutation.attemptedTxid === null
555
+ ? false
556
+ : await options.rpc.getRawTransaction(options.mutation.attemptedTxid, true).then(() => true).catch(() => false);
557
+ if (known) {
558
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
559
+ const live = updateMutationRecord(options.mutation, "live", options.nowUnixMs, {
560
+ temporaryBuilderLockedOutpoints: [],
561
+ });
562
+ let nextState = upsertPendingMutation(options.state, live);
563
+ nextState = await saveUpdatedState({
564
+ state: nextState,
565
+ provider: options.provider,
566
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
567
+ nowUnixMs: options.nowUnixMs,
568
+ paths: options.paths,
569
+ });
570
+ return { state: nextState, mutation: live, resolution: "live" };
571
+ }
572
+ if (options.mutation.status === "broadcast-unknown"
573
+ || options.mutation.status === "draft"
574
+ || options.mutation.status === "broadcasting") {
575
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
576
+ const canceled = updateMutationRecord(options.mutation, "canceled", options.nowUnixMs, {
577
+ temporaryBuilderLockedOutpoints: [],
578
+ });
579
+ let nextState = upsertPendingMutation(options.state, canceled);
580
+ nextState = await saveUpdatedState({
581
+ state: nextState,
582
+ provider: options.provider,
583
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
584
+ nowUnixMs: options.nowUnixMs,
585
+ paths: options.paths,
586
+ });
587
+ return { state: nextState, mutation: canceled, resolution: "not-seen" };
588
+ }
589
+ return {
590
+ state: options.state,
591
+ mutation: options.mutation,
592
+ resolution: "continue",
593
+ };
594
+ }
595
+ async function reconcileFieldFamily(options) {
596
+ const domainName = options.family.domainName ?? "";
597
+ const fieldName = options.family.fieldName ?? "";
598
+ const observed = getObservedFieldState(options.context, domainName, fieldName);
599
+ if (observed !== null) {
600
+ if (observed.fieldId === options.family.expectedFieldId
601
+ && observed.permanent === options.family.fieldPermanent
602
+ && observed.hasValue
603
+ && observed.format === options.family.fieldFormat
604
+ && observed.rawValueHex === options.family.fieldValueHex) {
605
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx1?.temporaryBuilderLockedOutpoints ?? []);
606
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2?.temporaryBuilderLockedOutpoints ?? []);
607
+ let nextState = updateFieldFamilyState({
608
+ state: options.state,
609
+ family: options.family,
610
+ status: "confirmed",
611
+ currentStep: "tx2",
612
+ nowUnixMs: options.nowUnixMs,
613
+ tx1: options.family.tx1 == null
614
+ ? undefined
615
+ : { ...options.family.tx1, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
616
+ tx2: options.family.tx2 == null
617
+ ? undefined
618
+ : { ...options.family.tx2, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
619
+ });
620
+ nextState = await saveUpdatedState({
621
+ state: nextState,
622
+ provider: options.provider,
623
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
624
+ nowUnixMs: options.nowUnixMs,
625
+ paths: options.paths,
626
+ });
627
+ return {
628
+ state: nextState,
629
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
630
+ ...options.family,
631
+ status: "confirmed",
632
+ },
633
+ resolution: "confirmed",
634
+ };
635
+ }
636
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx1?.temporaryBuilderLockedOutpoints ?? []);
637
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2?.temporaryBuilderLockedOutpoints ?? []);
638
+ let nextState = updateFieldFamilyState({
639
+ state: options.state,
640
+ family: options.family,
641
+ status: "repair-required",
642
+ currentStep: "tx2",
643
+ nowUnixMs: options.nowUnixMs,
644
+ tx1: options.family.tx1 == null
645
+ ? undefined
646
+ : { ...options.family.tx1, temporaryBuilderLockedOutpoints: [] },
647
+ tx2: options.family.tx2 == null
648
+ ? undefined
649
+ : { ...options.family.tx2, temporaryBuilderLockedOutpoints: [] },
650
+ });
651
+ nextState = await saveUpdatedState({
652
+ state: nextState,
653
+ provider: options.provider,
654
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
655
+ nowUnixMs: options.nowUnixMs,
656
+ paths: options.paths,
657
+ });
658
+ return {
659
+ state: nextState,
660
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
661
+ ...options.family,
662
+ status: "repair-required",
663
+ },
664
+ resolution: "repair-required",
665
+ };
666
+ }
667
+ const tx2Known = options.family.tx2?.attemptedTxid == null
668
+ ? false
669
+ : await options.rpc.getRawTransaction(options.family.tx2.attemptedTxid, true).then(() => true).catch(() => false);
670
+ if (tx2Known) {
671
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2?.temporaryBuilderLockedOutpoints ?? []);
672
+ let nextState = updateFieldFamilyState({
673
+ state: options.state,
674
+ family: options.family,
675
+ status: "live",
676
+ currentStep: "tx2",
677
+ nowUnixMs: options.nowUnixMs,
678
+ tx2: options.family.tx2 == null
679
+ ? undefined
680
+ : { ...options.family.tx2, status: "live", temporaryBuilderLockedOutpoints: [] },
681
+ });
682
+ nextState = await saveUpdatedState({
683
+ state: nextState,
684
+ provider: options.provider,
685
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
686
+ nowUnixMs: options.nowUnixMs,
687
+ paths: options.paths,
688
+ });
689
+ return {
690
+ state: nextState,
691
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
692
+ ...options.family,
693
+ status: "live",
694
+ },
695
+ resolution: "live",
696
+ };
697
+ }
698
+ const tx1Known = options.family.tx1?.attemptedTxid == null
699
+ ? false
700
+ : await options.rpc.getRawTransaction(options.family.tx1.attemptedTxid, true).then(() => true).catch(() => false);
701
+ if (tx1Known) {
702
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx1?.temporaryBuilderLockedOutpoints ?? []);
703
+ let nextState = updateFieldFamilyState({
704
+ state: options.state,
705
+ family: options.family,
706
+ status: "live",
707
+ currentStep: "tx1",
708
+ nowUnixMs: options.nowUnixMs,
709
+ tx1: options.family.tx1 == null
710
+ ? undefined
711
+ : { ...options.family.tx1, status: "live", temporaryBuilderLockedOutpoints: [] },
712
+ });
713
+ nextState = await saveUpdatedState({
714
+ state: nextState,
715
+ provider: options.provider,
716
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
717
+ nowUnixMs: options.nowUnixMs,
718
+ paths: options.paths,
719
+ });
720
+ return {
721
+ state: nextState,
722
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
723
+ ...options.family,
724
+ status: "live",
725
+ },
726
+ resolution: "ready-for-tx2",
727
+ };
728
+ }
729
+ if (options.family.status === "broadcast-unknown"
730
+ || options.family.status === "draft"
731
+ || options.family.status === "broadcasting") {
732
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx1?.temporaryBuilderLockedOutpoints ?? []);
733
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2?.temporaryBuilderLockedOutpoints ?? []);
734
+ let nextState = updateFieldFamilyState({
735
+ state: options.state,
736
+ family: options.family,
737
+ status: "canceled",
738
+ currentStep: options.family.currentStep,
739
+ nowUnixMs: options.nowUnixMs,
740
+ tx1: options.family.tx1 == null
741
+ ? undefined
742
+ : { ...options.family.tx1, status: options.family.tx1.attemptedTxid === null ? "canceled" : options.family.tx1.status, temporaryBuilderLockedOutpoints: [] },
743
+ tx2: options.family.tx2 == null
744
+ ? undefined
745
+ : { ...options.family.tx2, status: options.family.tx2.attemptedTxid === null ? "canceled" : options.family.tx2.status, temporaryBuilderLockedOutpoints: [] },
746
+ });
747
+ nextState = await saveUpdatedState({
748
+ state: nextState,
749
+ provider: options.provider,
750
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
751
+ nowUnixMs: options.nowUnixMs,
752
+ paths: options.paths,
753
+ });
754
+ return {
755
+ state: nextState,
756
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
757
+ ...options.family,
758
+ status: "canceled",
759
+ },
760
+ resolution: "not-seen",
761
+ };
762
+ }
763
+ return {
764
+ state: options.state,
765
+ family: options.family,
766
+ resolution: "continue",
767
+ };
768
+ }
769
+ async function confirmYesNo(prompter, message, errorCode, options) {
770
+ await confirmSharedYesNo(prompter, message, {
771
+ assumeYes: options.assumeYes,
772
+ errorCode,
773
+ requiresTtyErrorCode: options.requiresTtyErrorCode,
774
+ });
775
+ }
776
+ async function confirmTyped(prompter, expected, prompt, errorCode, options) {
777
+ await confirmSharedTypedAcknowledgement(prompter, {
778
+ assumeYes: options.assumeYes,
779
+ expected,
780
+ prompt,
781
+ errorCode,
782
+ requiresTtyErrorCode: options.requiresTtyErrorCode,
783
+ typedAckRequiredErrorCode: options.typedAckRequiredErrorCode,
784
+ });
785
+ }
786
+ async function confirmFieldCreate(prompter, options) {
787
+ const fieldRef = `${options.domainName}:${options.fieldName}`;
788
+ prompter.writeLine(`Creating field "${fieldRef}" as ${options.permanent ? "permanent" : "mutable"}.`);
789
+ prompter.writeLine(`Resolved sender: ${options.sender.selector} (${options.sender.address})`);
790
+ if (options.value === null) {
791
+ prompter.writeLine("Path: standalone-field-reg");
792
+ prompter.writeLine(`Effect: ${describeFieldEffect({ kind: "create-empty-field", burnCogtoshi: "100" })}.`);
793
+ prompter.writeLine("This publishes a standalone FIELD_REG and burns 0.00000100 COG.");
794
+ await confirmYesNo(prompter, "The field will be created empty and the burn is not reversible.", "wallet_field_create_confirmation_rejected", {
795
+ assumeYes: options.assumeYes,
796
+ requiresTtyErrorCode: "wallet_field_create_requires_tty",
797
+ });
798
+ return;
799
+ }
800
+ prompter.writeLine("Path: field-reg-plus-data-update-family");
801
+ prompter.writeLine(`Effect: ${describeFieldEffect({
802
+ kind: "create-and-initialize-field",
803
+ tx1BurnCogtoshi: "100",
804
+ tx2AdditionalBurnCogtoshi: "1",
805
+ })}.`);
806
+ prompter.writeLine(`Value: format ${options.value.format}, ${options.value.value.length} bytes`);
807
+ prompter.writeLine(`Initial value format: ${options.value.formatLabel}`);
808
+ prompter.writeLine(`Initial value bytes: ${options.value.value.length}`);
809
+ prompter.writeLine("Warning: non-clear field values are public in the mempool and on-chain.");
810
+ prompter.writeLine("This uses the same-block FIELD_REG -> DATA_UPDATE family.");
811
+ prompter.writeLine("Tx1 burns 0.00000100 COG. Tx2 may burn an additional 0.00000001 COG.");
812
+ prompter.writeLine("Tx1 may confirm even if Tx2 later fails, is canceled, or needs repair.");
813
+ if (options.permanent) {
814
+ prompter.writeLine("This is the first non-clear value write to a permanent field.");
815
+ await confirmTyped(prompter, fieldRef, `Type ${fieldRef} to continue: `, "wallet_field_create_confirmation_rejected", {
816
+ assumeYes: options.assumeYes,
817
+ requiresTtyErrorCode: "wallet_field_create_requires_tty",
818
+ typedAckRequiredErrorCode: "wallet_field_create_typed_ack_required",
819
+ });
820
+ return;
821
+ }
822
+ await confirmYesNo(prompter, "This creates and initializes the field in the same block family.", "wallet_field_create_confirmation_rejected", {
823
+ assumeYes: options.assumeYes,
824
+ requiresTtyErrorCode: "wallet_field_create_requires_tty",
825
+ });
826
+ }
827
+ async function confirmFieldSet(prompter, options) {
828
+ const fieldRef = `${options.domainName}:${options.fieldName}`;
829
+ prompter.writeLine(`Updating field "${fieldRef}".`);
830
+ prompter.writeLine(`Resolved sender: ${options.sender.selector} (${options.sender.address})`);
831
+ prompter.writeLine("Path: standalone-data-update");
832
+ prompter.writeLine(`Effect: ${describeFieldEffect({ kind: "write-field-value", burnCogtoshi: "1" })}.`);
833
+ prompter.writeLine(`Format: ${options.value.formatLabel}`);
834
+ prompter.writeLine(`Value bytes: ${options.value.value.length}`);
835
+ prompter.writeLine("Warning: the field value is public in the mempool and on-chain.");
836
+ if (options.isFirstPermanentWrite && options.fieldPermanent) {
837
+ prompter.writeLine("This is the first non-clear value write to a permanent field.");
838
+ await confirmTyped(prompter, fieldRef, `Type ${fieldRef} to continue: `, "wallet_field_set_confirmation_rejected", {
839
+ assumeYes: options.assumeYes,
840
+ requiresTtyErrorCode: "wallet_field_set_requires_tty",
841
+ typedAckRequiredErrorCode: "wallet_field_set_typed_ack_required",
842
+ });
843
+ return;
844
+ }
845
+ await confirmYesNo(prompter, "This publishes a standalone DATA_UPDATE.", "wallet_field_set_confirmation_rejected", {
846
+ assumeYes: options.assumeYes,
847
+ requiresTtyErrorCode: "wallet_field_set_requires_tty",
848
+ });
849
+ }
850
+ function describeRawFormat(format) {
851
+ if (format === FIELD_FORMAT_BYTES.bytes) {
852
+ return "bytes (0x01)";
853
+ }
854
+ if (format === FIELD_FORMAT_BYTES.text) {
855
+ return "text (0x02)";
856
+ }
857
+ if (format === FIELD_FORMAT_BYTES.json) {
858
+ return "json (0x09)";
859
+ }
860
+ return `raw (0x${format.toString(16).padStart(2, "0")})`;
861
+ }
862
+ async function loadFieldValue(source) {
863
+ if (source.kind === "text") {
864
+ if (source.value.length === 0) {
865
+ throw new Error("wallet_field_value_missing");
866
+ }
867
+ const value = new TextEncoder().encode(source.value);
868
+ return {
869
+ format: FIELD_FORMAT_BYTES.text,
870
+ formatLabel: "text (0x02)",
871
+ value,
872
+ valueHex: Buffer.from(value).toString("hex"),
873
+ };
874
+ }
875
+ if (source.kind === "json") {
876
+ if (source.value.length === 0) {
877
+ throw new Error("wallet_field_value_missing");
878
+ }
879
+ try {
880
+ JSON.parse(source.value);
881
+ }
882
+ catch {
883
+ throw new Error("wallet_field_invalid_json");
884
+ }
885
+ const value = new TextEncoder().encode(source.value);
886
+ return {
887
+ format: FIELD_FORMAT_BYTES.json,
888
+ formatLabel: "json (0x09)",
889
+ value,
890
+ valueHex: Buffer.from(value).toString("hex"),
891
+ };
892
+ }
893
+ if (source.kind === "bytes") {
894
+ let value;
895
+ if (source.value.startsWith("hex:")) {
896
+ const payload = source.value.slice(4);
897
+ if (!/^[0-9a-f]+$/.test(payload) || payload.length % 2 !== 0) {
898
+ throw new Error("wallet_field_invalid_bytes");
899
+ }
900
+ value = Buffer.from(payload, "hex");
901
+ }
902
+ else if (source.value.startsWith("@")) {
903
+ const filePath = source.value.slice(1);
904
+ if (filePath.trim() === "") {
905
+ throw new Error("wallet_field_invalid_bytes");
906
+ }
907
+ value = await readFile(resolvePath(process.cwd(), filePath));
908
+ }
909
+ else {
910
+ throw new Error("wallet_field_invalid_bytes");
911
+ }
912
+ if (value.length === 0) {
913
+ throw new Error("wallet_field_value_missing");
914
+ }
915
+ return {
916
+ format: FIELD_FORMAT_BYTES.bytes,
917
+ formatLabel: "bytes (0x01)",
918
+ value,
919
+ valueHex: value.toString("hex"),
920
+ };
921
+ }
922
+ const match = /^raw:(\d{1,3})$/.exec(source.format);
923
+ if (match == null) {
924
+ throw new Error("wallet_field_invalid_raw_format");
925
+ }
926
+ const format = Number.parseInt(match[1], 10);
927
+ if (!Number.isInteger(format) || format < 0 || format > 0xff || format === FIELD_FORMAT_BYTES.clear) {
928
+ throw new Error("wallet_field_invalid_raw_format");
929
+ }
930
+ let value;
931
+ if (source.value.startsWith("hex:")) {
932
+ const payload = source.value.slice(4);
933
+ if (!/^[0-9a-f]+$/.test(payload) || payload.length % 2 !== 0) {
934
+ throw new Error("wallet_field_invalid_value");
935
+ }
936
+ value = Buffer.from(payload, "hex");
937
+ }
938
+ else if (source.value.startsWith("@")) {
939
+ const filePath = source.value.slice(1);
940
+ if (filePath.trim() === "") {
941
+ throw new Error("wallet_field_invalid_value");
942
+ }
943
+ value = await readFile(resolvePath(process.cwd(), filePath));
944
+ }
945
+ else if (source.value.startsWith("utf8:")) {
946
+ value = new TextEncoder().encode(source.value.slice(5));
947
+ }
948
+ else {
949
+ throw new Error("wallet_field_invalid_value");
950
+ }
951
+ if (value.length === 0) {
952
+ throw new Error("wallet_field_value_missing");
953
+ }
954
+ return {
955
+ format,
956
+ formatLabel: describeRawFormat(format),
957
+ value,
958
+ valueHex: Buffer.from(value).toString("hex"),
959
+ };
960
+ }
961
+ async function sendStandaloneMutation(options) {
962
+ let nextState = options.state;
963
+ const broadcasting = updateMutationRecord(options.mutation, "broadcasting", options.nowUnixMs, {
964
+ attemptedTxid: options.built.txid,
965
+ attemptedWtxid: options.built.wtxid,
966
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
967
+ });
968
+ nextState = upsertPendingMutation(nextState, broadcasting);
969
+ nextState = await saveUpdatedState({
970
+ state: nextState,
971
+ provider: options.provider,
972
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
973
+ nowUnixMs: options.nowUnixMs,
974
+ paths: options.paths,
975
+ });
976
+ if (options.snapshotHeight !== null && options.snapshotHeight !== (await options.rpc.getBlockchainInfo()).blocks) {
977
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
978
+ throw new Error(`${options.errorPrefix}_tip_mismatch`);
979
+ }
980
+ try {
981
+ await options.rpc.sendRawTransaction(options.built.rawHex);
982
+ }
983
+ catch (error) {
984
+ if (!isAlreadyAcceptedError(error)) {
985
+ if (isBroadcastUnknownError(error)) {
986
+ const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", options.nowUnixMs, {
987
+ attemptedTxid: options.built.txid,
988
+ attemptedWtxid: options.built.wtxid,
989
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
990
+ });
991
+ nextState = upsertPendingMutation(nextState, unknown);
992
+ nextState = await saveUpdatedState({
993
+ state: nextState,
994
+ provider: options.provider,
995
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
996
+ nowUnixMs: options.nowUnixMs,
997
+ paths: options.paths,
998
+ });
999
+ throw new Error(`${options.errorPrefix}_broadcast_unknown`);
1000
+ }
1001
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1002
+ const canceled = updateMutationRecord(broadcasting, "canceled", options.nowUnixMs, {
1003
+ attemptedTxid: options.built.txid,
1004
+ attemptedWtxid: options.built.wtxid,
1005
+ temporaryBuilderLockedOutpoints: [],
1006
+ });
1007
+ nextState = upsertPendingMutation(nextState, canceled);
1008
+ nextState = await saveUpdatedState({
1009
+ state: nextState,
1010
+ provider: options.provider,
1011
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1012
+ nowUnixMs: options.nowUnixMs,
1013
+ paths: options.paths,
1014
+ });
1015
+ throw error;
1016
+ }
1017
+ }
1018
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1019
+ const live = updateMutationRecord(broadcasting, "live", options.nowUnixMs, {
1020
+ attemptedTxid: options.built.txid,
1021
+ attemptedWtxid: options.built.wtxid,
1022
+ temporaryBuilderLockedOutpoints: [],
1023
+ });
1024
+ nextState = upsertPendingMutation(nextState, live);
1025
+ nextState = await saveUpdatedState({
1026
+ state: nextState,
1027
+ provider: options.provider,
1028
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1029
+ nowUnixMs: options.nowUnixMs,
1030
+ paths: options.paths,
1031
+ });
1032
+ return { state: nextState, mutation: live };
1033
+ }
1034
+ async function sendFamilyTx1(options) {
1035
+ let nextState = updateFieldFamilyState({
1036
+ state: options.state,
1037
+ family: options.family,
1038
+ status: "broadcasting",
1039
+ currentStep: "tx1",
1040
+ nowUnixMs: options.nowUnixMs,
1041
+ tx1: {
1042
+ status: "broadcasting",
1043
+ attemptedTxid: options.built.txid,
1044
+ attemptedWtxid: options.built.wtxid,
1045
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
1046
+ rawHex: options.built.rawHex,
1047
+ },
1048
+ });
1049
+ nextState = await saveUpdatedState({
1050
+ state: nextState,
1051
+ provider: options.provider,
1052
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1053
+ nowUnixMs: options.nowUnixMs,
1054
+ paths: options.paths,
1055
+ });
1056
+ if (options.snapshotHeight !== null && options.snapshotHeight !== (await options.rpc.getBlockchainInfo()).blocks) {
1057
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1058
+ throw new Error("wallet_field_create_tx1_tip_mismatch");
1059
+ }
1060
+ try {
1061
+ await options.rpc.sendRawTransaction(options.built.rawHex);
1062
+ }
1063
+ catch (error) {
1064
+ if (!isAlreadyAcceptedError(error)) {
1065
+ if (isBroadcastUnknownError(error)) {
1066
+ nextState = updateFieldFamilyState({
1067
+ state: nextState,
1068
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1069
+ status: "broadcast-unknown",
1070
+ currentStep: "tx1",
1071
+ nowUnixMs: options.nowUnixMs,
1072
+ tx1: {
1073
+ status: "broadcast-unknown",
1074
+ attemptedTxid: options.built.txid,
1075
+ attemptedWtxid: options.built.wtxid,
1076
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
1077
+ rawHex: options.built.rawHex,
1078
+ },
1079
+ });
1080
+ nextState = await saveUpdatedState({
1081
+ state: nextState,
1082
+ provider: options.provider,
1083
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1084
+ nowUnixMs: options.nowUnixMs,
1085
+ paths: options.paths,
1086
+ });
1087
+ throw new Error("wallet_field_create_tx1_broadcast_unknown");
1088
+ }
1089
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1090
+ nextState = updateFieldFamilyState({
1091
+ state: nextState,
1092
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1093
+ status: "canceled",
1094
+ currentStep: "tx1",
1095
+ nowUnixMs: options.nowUnixMs,
1096
+ tx1: {
1097
+ status: "canceled",
1098
+ attemptedTxid: options.built.txid,
1099
+ attemptedWtxid: options.built.wtxid,
1100
+ temporaryBuilderLockedOutpoints: [],
1101
+ rawHex: options.built.rawHex,
1102
+ },
1103
+ });
1104
+ nextState = await saveUpdatedState({
1105
+ state: nextState,
1106
+ provider: options.provider,
1107
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1108
+ nowUnixMs: options.nowUnixMs,
1109
+ paths: options.paths,
1110
+ });
1111
+ throw error;
1112
+ }
1113
+ }
1114
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1115
+ nextState = updateFieldFamilyState({
1116
+ state: nextState,
1117
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1118
+ status: "live",
1119
+ currentStep: "tx1",
1120
+ nowUnixMs: options.nowUnixMs,
1121
+ tx1: {
1122
+ status: "live",
1123
+ attemptedTxid: options.built.txid,
1124
+ attemptedWtxid: options.built.wtxid,
1125
+ temporaryBuilderLockedOutpoints: [],
1126
+ rawHex: options.built.rawHex,
1127
+ },
1128
+ });
1129
+ nextState = await saveUpdatedState({
1130
+ state: nextState,
1131
+ provider: options.provider,
1132
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1133
+ nowUnixMs: options.nowUnixMs,
1134
+ paths: options.paths,
1135
+ });
1136
+ return {
1137
+ state: nextState,
1138
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
1139
+ ...options.family,
1140
+ status: "live",
1141
+ },
1142
+ };
1143
+ }
1144
+ async function sendFamilyTx2(options) {
1145
+ let nextState = updateFieldFamilyState({
1146
+ state: options.state,
1147
+ family: options.family,
1148
+ status: "broadcasting",
1149
+ currentStep: "tx2",
1150
+ nowUnixMs: options.nowUnixMs,
1151
+ tx2: {
1152
+ status: "broadcasting",
1153
+ attemptedTxid: options.built.txid,
1154
+ attemptedWtxid: options.built.wtxid,
1155
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
1156
+ rawHex: options.built.rawHex,
1157
+ },
1158
+ });
1159
+ nextState = await saveUpdatedState({
1160
+ state: nextState,
1161
+ provider: options.provider,
1162
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1163
+ nowUnixMs: options.nowUnixMs,
1164
+ paths: options.paths,
1165
+ });
1166
+ try {
1167
+ await options.rpc.sendRawTransaction(options.built.rawHex);
1168
+ }
1169
+ catch (error) {
1170
+ if (!isAlreadyAcceptedError(error)) {
1171
+ if (isBroadcastUnknownError(error)) {
1172
+ nextState = updateFieldFamilyState({
1173
+ state: nextState,
1174
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1175
+ status: "broadcast-unknown",
1176
+ currentStep: "tx2",
1177
+ nowUnixMs: options.nowUnixMs,
1178
+ tx2: {
1179
+ status: "broadcast-unknown",
1180
+ attemptedTxid: options.built.txid,
1181
+ attemptedWtxid: options.built.wtxid,
1182
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
1183
+ rawHex: options.built.rawHex,
1184
+ },
1185
+ });
1186
+ nextState = await saveUpdatedState({
1187
+ state: nextState,
1188
+ provider: options.provider,
1189
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1190
+ nowUnixMs: options.nowUnixMs,
1191
+ paths: options.paths,
1192
+ });
1193
+ throw new Error("wallet_field_create_tx2_broadcast_unknown");
1194
+ }
1195
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1196
+ nextState = updateFieldFamilyState({
1197
+ state: nextState,
1198
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1199
+ status: "live",
1200
+ currentStep: "tx1",
1201
+ nowUnixMs: options.nowUnixMs,
1202
+ tx2: {
1203
+ status: "canceled",
1204
+ attemptedTxid: options.built.txid,
1205
+ attemptedWtxid: options.built.wtxid,
1206
+ temporaryBuilderLockedOutpoints: [],
1207
+ rawHex: options.built.rawHex,
1208
+ },
1209
+ });
1210
+ nextState = await saveUpdatedState({
1211
+ state: nextState,
1212
+ provider: options.provider,
1213
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1214
+ nowUnixMs: options.nowUnixMs,
1215
+ paths: options.paths,
1216
+ });
1217
+ throw error;
1218
+ }
1219
+ }
1220
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
1221
+ nextState = updateFieldFamilyState({
1222
+ state: nextState,
1223
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? options.family,
1224
+ status: "live",
1225
+ currentStep: "tx2",
1226
+ nowUnixMs: options.nowUnixMs,
1227
+ tx2: {
1228
+ status: "live",
1229
+ attemptedTxid: options.built.txid,
1230
+ attemptedWtxid: options.built.wtxid,
1231
+ temporaryBuilderLockedOutpoints: [],
1232
+ rawHex: options.built.rawHex,
1233
+ },
1234
+ });
1235
+ nextState = await saveUpdatedState({
1236
+ state: nextState,
1237
+ provider: options.provider,
1238
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
1239
+ nowUnixMs: options.nowUnixMs,
1240
+ paths: options.paths,
1241
+ });
1242
+ return {
1243
+ state: nextState,
1244
+ family: findFieldFamilyByIntent(nextState, options.family.intentFingerprintHex) ?? {
1245
+ ...options.family,
1246
+ status: "live",
1247
+ },
1248
+ };
1249
+ }
1250
+ async function submitStandaloneFieldMutation(options) {
1251
+ if (!options.prompter.isInteractive && options.assumeYes !== true) {
1252
+ throw new Error(`${options.errorPrefix}_requires_tty`);
1253
+ }
1254
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
1255
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
1256
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1257
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1258
+ purpose: options.errorPrefix,
1259
+ walletRootId: null,
1260
+ });
1261
+ try {
1262
+ const miningPreemption = await pauseMiningForWalletMutation({
1263
+ paths,
1264
+ reason: options.errorPrefix,
1265
+ });
1266
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
1267
+ dataDir: options.dataDir,
1268
+ databasePath: options.databasePath,
1269
+ secretProvider: provider,
1270
+ paths,
1271
+ });
1272
+ try {
1273
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1274
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1275
+ const operation = resolveAnchoredFieldOperation(readContext, normalizedDomainName, options.errorPrefix);
1276
+ const existingObservedField = getObservedFieldState(readContext, normalizedDomainName, normalizedFieldName);
1277
+ const intentFingerprintHex = createIntentFingerprint([
1278
+ options.kind,
1279
+ operation.state.walletRootId,
1280
+ normalizedDomainName,
1281
+ normalizedFieldName,
1282
+ ]);
1283
+ const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
1284
+ if (existingMutation !== null) {
1285
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1286
+ dataDir: options.dataDir,
1287
+ chain: "main",
1288
+ startHeight: 0,
1289
+ walletRootId: operation.state.walletRootId,
1290
+ });
1291
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1292
+ const walletName = operation.state.managedCoreWallet.walletName;
1293
+ const reconciled = await reconcilePendingFieldMutation({
1294
+ state: operation.state,
1295
+ mutation: existingMutation,
1296
+ provider,
1297
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1298
+ nowUnixMs,
1299
+ paths,
1300
+ rpc,
1301
+ walletName,
1302
+ context: readContext,
1303
+ });
1304
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
1305
+ return {
1306
+ kind: options.kind,
1307
+ domainName: normalizedDomainName,
1308
+ fieldName: normalizedFieldName,
1309
+ fieldId: reconciled.mutation.fieldId ?? existingObservedField?.fieldId ?? null,
1310
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
1311
+ family: false,
1312
+ permanent: reconciled.mutation.fieldPermanent ?? existingObservedField?.permanent ?? null,
1313
+ format: reconciled.mutation.fieldFormat ?? existingObservedField?.format ?? null,
1314
+ status: reconciled.resolution,
1315
+ reusedExisting: true,
1316
+ resolved: createResolvedFieldSummary({
1317
+ sender: operation.sender,
1318
+ senderSelector: operation.senderSelector,
1319
+ kind: options.kind,
1320
+ family: false,
1321
+ value: createResolvedFieldValueFromStoredData(options.kind, reconciled.mutation.fieldFormat ?? existingObservedField?.format ?? null, reconciled.mutation.fieldValueHex),
1322
+ }),
1323
+ };
1324
+ }
1325
+ if (reconciled.resolution === "repair-required") {
1326
+ throw new Error(`${options.errorPrefix}_repair_required`);
1327
+ }
1328
+ }
1329
+ await options.confirm(operation);
1330
+ const planned = await options.createMutation(operation, existingMutation);
1331
+ let nextState = upsertPendingMutation(operation.state, planned.mutation);
1332
+ nextState = await saveUpdatedState({
1333
+ state: nextState,
1334
+ provider,
1335
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1336
+ nowUnixMs,
1337
+ paths,
1338
+ });
1339
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1340
+ dataDir: options.dataDir,
1341
+ chain: "main",
1342
+ startHeight: 0,
1343
+ walletRootId: operation.state.walletRootId,
1344
+ });
1345
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1346
+ const walletName = operation.state.managedCoreWallet.walletName;
1347
+ const built = await buildFieldTransaction({
1348
+ rpc,
1349
+ walletName,
1350
+ plan: buildAnchoredFieldPlan({
1351
+ state: nextState,
1352
+ allUtxos: await rpc.listUnspent(walletName, 1),
1353
+ sender: operation.sender,
1354
+ anchorOutpoint: operation.anchorOutpoint,
1355
+ opReturnData: planned.opReturnData,
1356
+ errorPrefix: options.errorPrefix,
1357
+ }),
1358
+ });
1359
+ const final = await sendStandaloneMutation({
1360
+ rpc,
1361
+ walletName,
1362
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
1363
+ built,
1364
+ mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === planned.mutation.intentFingerprintHex),
1365
+ state: nextState,
1366
+ provider,
1367
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1368
+ nowUnixMs,
1369
+ paths,
1370
+ errorPrefix: options.errorPrefix,
1371
+ });
1372
+ return {
1373
+ kind: options.kind,
1374
+ domainName: normalizedDomainName,
1375
+ fieldName: normalizedFieldName,
1376
+ fieldId: final.mutation.fieldId ?? existingObservedField?.fieldId ?? null,
1377
+ txid: final.mutation.attemptedTxid ?? built.txid,
1378
+ family: false,
1379
+ permanent: final.mutation.fieldPermanent ?? existingObservedField?.permanent ?? null,
1380
+ format: final.mutation.fieldFormat ?? existingObservedField?.format ?? null,
1381
+ status: "live",
1382
+ reusedExisting: false,
1383
+ resolved: createResolvedFieldSummary({
1384
+ sender: operation.sender,
1385
+ senderSelector: operation.senderSelector,
1386
+ kind: options.kind,
1387
+ family: false,
1388
+ value: createResolvedFieldValueFromStoredData(options.kind, planned.mutation.fieldFormat ?? existingObservedField?.format ?? null, planned.mutation.fieldValueHex),
1389
+ }),
1390
+ };
1391
+ }
1392
+ finally {
1393
+ await readContext.close();
1394
+ await miningPreemption.release();
1395
+ }
1396
+ }
1397
+ finally {
1398
+ await controlLock.release();
1399
+ }
1400
+ }
1401
+ async function submitFieldCreateFamily(options) {
1402
+ if (!options.prompter.isInteractive && options.assumeYes !== true) {
1403
+ throw new Error("wallet_field_create_requires_tty");
1404
+ }
1405
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
1406
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
1407
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1408
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1409
+ purpose: "wallet_field_create",
1410
+ walletRootId: null,
1411
+ });
1412
+ try {
1413
+ const miningPreemption = await pauseMiningForWalletMutation({
1414
+ paths,
1415
+ reason: "wallet_field_create",
1416
+ });
1417
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
1418
+ dataDir: options.dataDir,
1419
+ databasePath: options.databasePath,
1420
+ secretProvider: provider,
1421
+ paths,
1422
+ });
1423
+ try {
1424
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1425
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1426
+ const operation = resolveAnchoredFieldOperation(readContext, normalizedDomainName, "wallet_field_create");
1427
+ const existingField = getObservedFieldState(readContext, normalizedDomainName, normalizedFieldName);
1428
+ if (existingField !== null) {
1429
+ throw new Error("wallet_field_create_field_exists");
1430
+ }
1431
+ if (operation.chainDomain.nextFieldId === 0xffff_ffff) {
1432
+ throw new Error("wallet_field_create_field_id_exhausted");
1433
+ }
1434
+ if (hex(operation.chainDomain.delegate) !== null) {
1435
+ throw new Error("wallet_field_create_delegate_blocks_same_block_family");
1436
+ }
1437
+ const senderBalance = getBalance(operation.readContext.snapshot.state, Buffer.from(operation.sender.scriptPubKeyHex, "hex"));
1438
+ if (senderBalance < 101n) {
1439
+ throw new Error("wallet_field_create_insufficient_cog");
1440
+ }
1441
+ const intentFingerprintHex = createIntentFingerprint([
1442
+ "field-create",
1443
+ operation.state.walletRootId,
1444
+ normalizedDomainName,
1445
+ normalizedFieldName,
1446
+ options.permanent ? 1 : 0,
1447
+ options.value.format,
1448
+ options.value.valueHex,
1449
+ ]);
1450
+ const existingFamily = findFieldFamilyByIntent(operation.state, intentFingerprintHex);
1451
+ const conflictingFamily = findActiveFieldFamilyByDomain(operation.state, normalizedDomainName);
1452
+ if (conflictingFamily !== null && conflictingFamily.intentFingerprintHex !== intentFingerprintHex) {
1453
+ throw new Error("wallet_field_create_family_already_active");
1454
+ }
1455
+ const conflictingCreate = findActiveFieldCreateMutationByDomain(operation.state, normalizedDomainName, intentFingerprintHex);
1456
+ if (conflictingCreate !== null) {
1457
+ throw new Error("wallet_field_create_registration_already_pending");
1458
+ }
1459
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
1460
+ dataDir: options.dataDir,
1461
+ chain: "main",
1462
+ startHeight: 0,
1463
+ walletRootId: operation.state.walletRootId,
1464
+ });
1465
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
1466
+ const walletName = operation.state.managedCoreWallet.walletName;
1467
+ let workingState = operation.state;
1468
+ let resumedFamily = null;
1469
+ if (existingFamily !== null) {
1470
+ const reconciled = await reconcileFieldFamily({
1471
+ state: workingState,
1472
+ family: existingFamily,
1473
+ provider,
1474
+ nowUnixMs,
1475
+ paths,
1476
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1477
+ rpc,
1478
+ walletName,
1479
+ context: readContext,
1480
+ });
1481
+ workingState = reconciled.state;
1482
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
1483
+ return {
1484
+ kind: "field-create",
1485
+ domainName: normalizedDomainName,
1486
+ fieldName: normalizedFieldName,
1487
+ fieldId: reconciled.family.expectedFieldId ?? null,
1488
+ txid: reconciled.family.tx2?.attemptedTxid ?? reconciled.family.tx1?.attemptedTxid ?? "unknown",
1489
+ tx1Txid: reconciled.family.tx1?.attemptedTxid ?? null,
1490
+ tx2Txid: reconciled.family.tx2?.attemptedTxid ?? null,
1491
+ family: true,
1492
+ permanent: reconciled.family.fieldPermanent ?? null,
1493
+ format: reconciled.family.fieldFormat ?? null,
1494
+ status: reconciled.resolution,
1495
+ reusedExisting: true,
1496
+ resolved: createResolvedFieldSummary({
1497
+ sender: operation.sender,
1498
+ senderSelector: operation.senderSelector,
1499
+ kind: "field-create",
1500
+ family: true,
1501
+ value: createResolvedFieldValueFromStoredData("field-create", reconciled.family.fieldFormat ?? null, reconciled.family.fieldValueHex),
1502
+ }),
1503
+ };
1504
+ }
1505
+ if (reconciled.resolution === "repair-required") {
1506
+ throw new Error("wallet_field_create_repair_required");
1507
+ }
1508
+ if (reconciled.resolution === "ready-for-tx2") {
1509
+ resumedFamily = reconciled.family;
1510
+ }
1511
+ }
1512
+ if (resumedFamily === null) {
1513
+ await confirmFieldCreate(options.prompter, {
1514
+ domainName: normalizedDomainName,
1515
+ fieldName: normalizedFieldName,
1516
+ permanent: options.permanent,
1517
+ value: options.value,
1518
+ sender: createResolvedFieldSenderSummary(operation.sender, operation.senderSelector),
1519
+ assumeYes: options.assumeYes,
1520
+ });
1521
+ let nextState = upsertProactiveFamily(workingState, createFieldFamilyRecord({
1522
+ domainName: normalizedDomainName,
1523
+ domainId: operation.chainDomain.domainId,
1524
+ fieldName: normalizedFieldName,
1525
+ expectedFieldId: operation.chainDomain.nextFieldId,
1526
+ sender: operation.sender,
1527
+ permanence: options.permanent,
1528
+ format: options.value.format,
1529
+ valueHex: options.value.valueHex,
1530
+ intentFingerprintHex,
1531
+ nowUnixMs,
1532
+ existing: existingFamily,
1533
+ }));
1534
+ nextState = await saveUpdatedState({
1535
+ state: nextState,
1536
+ provider,
1537
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1538
+ nowUnixMs,
1539
+ paths,
1540
+ });
1541
+ const family = findFieldFamilyByIntent(nextState, intentFingerprintHex);
1542
+ const tx1 = await buildFieldTransaction({
1543
+ rpc,
1544
+ walletName,
1545
+ plan: buildAnchoredFieldPlan({
1546
+ state: nextState,
1547
+ allUtxos: await rpc.listUnspent(walletName, 1),
1548
+ sender: operation.sender,
1549
+ anchorOutpoint: operation.anchorOutpoint,
1550
+ opReturnData: serializeFieldReg(operation.chainDomain.domainId, options.permanent, normalizedFieldName).opReturnData,
1551
+ errorPrefix: "wallet_field_create_tx1",
1552
+ }),
1553
+ });
1554
+ const afterTx1 = await sendFamilyTx1({
1555
+ rpc,
1556
+ walletName,
1557
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
1558
+ built: tx1,
1559
+ family,
1560
+ state: nextState,
1561
+ provider,
1562
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1563
+ nowUnixMs,
1564
+ paths,
1565
+ });
1566
+ workingState = afterTx1.state;
1567
+ resumedFamily = afterTx1.family;
1568
+ }
1569
+ const tx1Txid = resumedFamily.tx1?.attemptedTxid;
1570
+ if (tx1Txid == null) {
1571
+ throw new Error("wallet_field_create_tx1_missing");
1572
+ }
1573
+ await rpc.getRawTransaction(tx1Txid, true);
1574
+ const tx2 = await buildFieldTransaction({
1575
+ rpc,
1576
+ walletName,
1577
+ plan: buildFieldFamilyTx2Plan({
1578
+ state: workingState,
1579
+ allUtxos: await rpc.listUnspent(walletName, 1),
1580
+ sender: operation.sender,
1581
+ tx1Txid,
1582
+ opReturnData: serializeDataUpdate(operation.chainDomain.domainId, resumedFamily.expectedFieldId ?? operation.chainDomain.nextFieldId, options.value.format, options.value.value).opReturnData,
1583
+ }),
1584
+ builderOptions: {
1585
+ includeUnsafe: true,
1586
+ minConf: 0,
1587
+ },
1588
+ });
1589
+ const final = await sendFamilyTx2({
1590
+ rpc,
1591
+ walletName,
1592
+ built: tx2,
1593
+ family: resumedFamily,
1594
+ state: workingState,
1595
+ provider,
1596
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1597
+ nowUnixMs,
1598
+ paths,
1599
+ });
1600
+ return {
1601
+ kind: "field-create",
1602
+ domainName: normalizedDomainName,
1603
+ fieldName: normalizedFieldName,
1604
+ fieldId: final.family.expectedFieldId ?? null,
1605
+ txid: final.family.tx2?.attemptedTxid ?? tx2.txid,
1606
+ tx1Txid,
1607
+ tx2Txid: final.family.tx2?.attemptedTxid ?? tx2.txid,
1608
+ family: true,
1609
+ permanent: final.family.fieldPermanent ?? null,
1610
+ format: final.family.fieldFormat ?? null,
1611
+ status: "live",
1612
+ reusedExisting: existingFamily !== null,
1613
+ resolved: createResolvedFieldSummary({
1614
+ sender: operation.sender,
1615
+ senderSelector: operation.senderSelector,
1616
+ kind: "field-create",
1617
+ family: true,
1618
+ value: createResolvedFieldValueSummary(options.value.format, options.value.value),
1619
+ }),
1620
+ };
1621
+ }
1622
+ finally {
1623
+ await readContext.close();
1624
+ await miningPreemption.release();
1625
+ }
1626
+ }
1627
+ finally {
1628
+ await controlLock.release();
1629
+ }
1630
+ }
1631
+ export async function createField(options) {
1632
+ const normalizedSource = options.source == null ? null : await loadFieldValue(options.source);
1633
+ const permanent = options.permanent ?? false;
1634
+ if (normalizedSource !== null) {
1635
+ return submitFieldCreateFamily({
1636
+ ...options,
1637
+ permanent,
1638
+ value: normalizedSource,
1639
+ });
1640
+ }
1641
+ return submitStandaloneFieldMutation({
1642
+ kind: "field-create",
1643
+ errorPrefix: "wallet_field_create",
1644
+ domainName: options.domainName,
1645
+ fieldName: options.fieldName,
1646
+ dataDir: options.dataDir,
1647
+ databasePath: options.databasePath,
1648
+ provider: options.provider,
1649
+ prompter: options.prompter,
1650
+ assumeYes: options.assumeYes,
1651
+ nowUnixMs: options.nowUnixMs,
1652
+ paths: options.paths,
1653
+ openReadContext: options.openReadContext,
1654
+ attachService: options.attachService,
1655
+ rpcFactory: options.rpcFactory,
1656
+ async createMutation(operation, existing) {
1657
+ const existingField = getObservedFieldState(operation.readContext, normalizeDomainName(options.domainName), normalizeFieldName(options.fieldName));
1658
+ if (existingField !== null) {
1659
+ throw new Error("wallet_field_create_field_exists");
1660
+ }
1661
+ if (operation.chainDomain.nextFieldId === 0xffff_ffff) {
1662
+ throw new Error("wallet_field_create_field_id_exhausted");
1663
+ }
1664
+ const senderBalance = getBalance(operation.readContext.snapshot.state, Buffer.from(operation.sender.scriptPubKeyHex, "hex"));
1665
+ if (senderBalance < 100n) {
1666
+ throw new Error("wallet_field_create_insufficient_cog");
1667
+ }
1668
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1669
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1670
+ const intentFingerprintHex = createIntentFingerprint([
1671
+ "field-create",
1672
+ operation.state.walletRootId,
1673
+ normalizedDomainName,
1674
+ normalizedFieldName,
1675
+ permanent ? 1 : 0,
1676
+ ]);
1677
+ const conflictFamily = findActiveFieldFamilyByDomain(operation.state, normalizedDomainName);
1678
+ if (conflictFamily !== null && conflictFamily.intentFingerprintHex !== intentFingerprintHex) {
1679
+ throw new Error("wallet_field_create_family_already_active");
1680
+ }
1681
+ const conflictCreate = findActiveFieldCreateMutationByDomain(operation.state, normalizedDomainName, intentFingerprintHex);
1682
+ if (conflictCreate !== null) {
1683
+ throw new Error("wallet_field_create_registration_already_pending");
1684
+ }
1685
+ return {
1686
+ opReturnData: serializeFieldReg(operation.chainDomain.domainId, permanent, normalizedFieldName).opReturnData,
1687
+ mutation: createStandaloneFieldMutation({
1688
+ kind: "field-create",
1689
+ domainName: normalizedDomainName,
1690
+ fieldName: normalizedFieldName,
1691
+ sender: operation.sender,
1692
+ intentFingerprintHex,
1693
+ nowUnixMs: options.nowUnixMs ?? Date.now(),
1694
+ existing,
1695
+ fieldId: operation.chainDomain.nextFieldId,
1696
+ fieldPermanent: permanent,
1697
+ }),
1698
+ };
1699
+ },
1700
+ async confirm(operation) {
1701
+ await confirmFieldCreate(options.prompter, {
1702
+ domainName: normalizeDomainName(options.domainName),
1703
+ fieldName: normalizeFieldName(options.fieldName),
1704
+ permanent,
1705
+ value: null,
1706
+ sender: createResolvedFieldSenderSummary(operation.sender, operation.senderSelector),
1707
+ assumeYes: options.assumeYes,
1708
+ });
1709
+ },
1710
+ });
1711
+ }
1712
+ export async function setField(options) {
1713
+ const value = await loadFieldValue(options.source);
1714
+ return submitStandaloneFieldMutation({
1715
+ kind: "field-set",
1716
+ errorPrefix: "wallet_field_set",
1717
+ domainName: options.domainName,
1718
+ fieldName: options.fieldName,
1719
+ dataDir: options.dataDir,
1720
+ databasePath: options.databasePath,
1721
+ provider: options.provider,
1722
+ prompter: options.prompter,
1723
+ assumeYes: options.assumeYes,
1724
+ nowUnixMs: options.nowUnixMs,
1725
+ paths: options.paths,
1726
+ openReadContext: options.openReadContext,
1727
+ attachService: options.attachService,
1728
+ rpcFactory: options.rpcFactory,
1729
+ async createMutation(operation, existing) {
1730
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1731
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1732
+ const observedField = getObservedFieldState(operation.readContext, normalizedDomainName, normalizedFieldName);
1733
+ if (observedField === null) {
1734
+ throw new Error("wallet_field_set_field_not_found");
1735
+ }
1736
+ if (observedField.permanent && observedField.hasValue) {
1737
+ throw new Error("wallet_field_set_permanent_field_frozen");
1738
+ }
1739
+ const senderBalance = getBalance(operation.readContext.snapshot.state, Buffer.from(operation.sender.scriptPubKeyHex, "hex"));
1740
+ if (senderBalance < 1n) {
1741
+ throw new Error("wallet_field_set_insufficient_cog");
1742
+ }
1743
+ const intentFingerprintHex = createIntentFingerprint([
1744
+ "field-set",
1745
+ operation.state.walletRootId,
1746
+ normalizedDomainName,
1747
+ observedField.fieldId,
1748
+ value.format,
1749
+ value.valueHex,
1750
+ ]);
1751
+ return {
1752
+ opReturnData: serializeDataUpdate(operation.chainDomain.domainId, observedField.fieldId, value.format, value.value).opReturnData,
1753
+ mutation: createStandaloneFieldMutation({
1754
+ kind: "field-set",
1755
+ domainName: normalizedDomainName,
1756
+ fieldName: normalizedFieldName,
1757
+ sender: operation.sender,
1758
+ intentFingerprintHex,
1759
+ nowUnixMs: options.nowUnixMs ?? Date.now(),
1760
+ existing,
1761
+ fieldId: observedField.fieldId,
1762
+ fieldPermanent: observedField.permanent,
1763
+ fieldFormat: value.format,
1764
+ fieldValueHex: value.valueHex,
1765
+ }),
1766
+ };
1767
+ },
1768
+ async confirm(operation) {
1769
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1770
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1771
+ const observedField = getObservedFieldState(operation.readContext, normalizedDomainName, normalizedFieldName);
1772
+ if (observedField === null) {
1773
+ throw new Error("wallet_field_set_field_not_found");
1774
+ }
1775
+ await confirmFieldSet(options.prompter, {
1776
+ domainName: normalizedDomainName,
1777
+ fieldName: normalizedFieldName,
1778
+ fieldPermanent: observedField.permanent,
1779
+ isFirstPermanentWrite: observedField.permanent && !observedField.hasValue,
1780
+ value,
1781
+ sender: createResolvedFieldSenderSummary(operation.sender, operation.senderSelector),
1782
+ assumeYes: options.assumeYes,
1783
+ });
1784
+ },
1785
+ });
1786
+ }
1787
+ export async function clearField(options) {
1788
+ return submitStandaloneFieldMutation({
1789
+ kind: "field-clear",
1790
+ errorPrefix: "wallet_field_clear",
1791
+ domainName: options.domainName,
1792
+ fieldName: options.fieldName,
1793
+ dataDir: options.dataDir,
1794
+ databasePath: options.databasePath,
1795
+ provider: options.provider,
1796
+ prompter: options.prompter,
1797
+ assumeYes: options.assumeYes,
1798
+ nowUnixMs: options.nowUnixMs,
1799
+ paths: options.paths,
1800
+ openReadContext: options.openReadContext,
1801
+ attachService: options.attachService,
1802
+ rpcFactory: options.rpcFactory,
1803
+ async createMutation(operation, existing) {
1804
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1805
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1806
+ const observedField = getObservedFieldState(operation.readContext, normalizedDomainName, normalizedFieldName);
1807
+ if (observedField === null) {
1808
+ throw new Error("wallet_field_clear_field_not_found");
1809
+ }
1810
+ if (observedField.permanent && !observedField.hasValue) {
1811
+ throw new Error("wallet_field_clear_noop_permanent_clear");
1812
+ }
1813
+ const intentFingerprintHex = createIntentFingerprint([
1814
+ "field-clear",
1815
+ operation.state.walletRootId,
1816
+ normalizedDomainName,
1817
+ observedField.fieldId,
1818
+ ]);
1819
+ return {
1820
+ opReturnData: serializeDataUpdate(operation.chainDomain.domainId, observedField.fieldId, FIELD_FORMAT_BYTES.clear).opReturnData,
1821
+ mutation: createStandaloneFieldMutation({
1822
+ kind: "field-clear",
1823
+ domainName: normalizedDomainName,
1824
+ fieldName: normalizedFieldName,
1825
+ sender: operation.sender,
1826
+ intentFingerprintHex,
1827
+ nowUnixMs: options.nowUnixMs ?? Date.now(),
1828
+ existing,
1829
+ fieldId: observedField.fieldId,
1830
+ fieldPermanent: observedField.permanent,
1831
+ fieldFormat: FIELD_FORMAT_BYTES.clear,
1832
+ fieldValueHex: "",
1833
+ }),
1834
+ };
1835
+ },
1836
+ async confirm(operation) {
1837
+ const normalizedDomainName = normalizeDomainName(options.domainName);
1838
+ const normalizedFieldName = normalizeFieldName(options.fieldName);
1839
+ const observedField = getObservedFieldState(operation.readContext, normalizedDomainName, normalizedFieldName);
1840
+ if (observedField === null) {
1841
+ throw new Error("wallet_field_clear_field_not_found");
1842
+ }
1843
+ if (observedField.permanent && !observedField.hasValue) {
1844
+ throw new Error("wallet_field_clear_noop_permanent_clear");
1845
+ }
1846
+ options.prompter.writeLine(`Clearing field "${normalizedDomainName}:${normalizedFieldName}".`);
1847
+ options.prompter.writeLine(`Resolved sender: ${operation.senderSelector} (${operation.sender.address})`);
1848
+ options.prompter.writeLine("Path: standalone-data-clear");
1849
+ options.prompter.writeLine(`Effect: ${describeFieldEffect({ kind: "clear-field-value", burnCogtoshi: "0" })}.`);
1850
+ options.prompter.writeLine("This publishes a standalone DATA_UPDATE clear.");
1851
+ },
1852
+ });
1853
+ }