@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,1055 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { getBalance, getLock, lookupDomain } from "@cogcoin/indexer/queries";
3
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
4
+ import { createRpcClient } from "../../bitcoind/node.js";
5
+ import { acquireFileLock } from "../fs/lock.js";
6
+ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
7
+ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
8
+ import { serializeCogClaim, serializeCogLock, serializeCogTransfer, } from "../cogop/index.js";
9
+ import { openWalletReadContext } from "../read/index.js";
10
+ import { assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
11
+ import { confirmTypedAcknowledgement, confirmYesNo } from "./confirm.js";
12
+ import { getCanonicalIdentitySelector, resolveIdentityBySelector, } from "./identity-selector.js";
13
+ import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
14
+ import { normalizeBtcTarget } from "./targets.js";
15
+ const MAX_LOCK_DURATION_BLOCKS = 262_800;
16
+ const ZERO_PREIMAGE_HEX = "00".repeat(32);
17
+ function normalizeDomainName(value) {
18
+ const normalized = value.trim().toLowerCase();
19
+ if (normalized.length === 0) {
20
+ throw new Error("wallet_cog_missing_domain");
21
+ }
22
+ return normalized;
23
+ }
24
+ function normalizePositiveAmount(amountCogtoshi, errorCode) {
25
+ if (amountCogtoshi <= 0n) {
26
+ throw new Error(errorCode);
27
+ }
28
+ return amountCogtoshi;
29
+ }
30
+ function encodeOpReturnScript(payload) {
31
+ if (payload.length <= 75) {
32
+ return Buffer.concat([
33
+ Buffer.from([0x6a, payload.length]),
34
+ Buffer.from(payload),
35
+ ]).toString("hex");
36
+ }
37
+ return Buffer.concat([
38
+ Buffer.from([0x6a, 0x4c, payload.length]),
39
+ Buffer.from(payload),
40
+ ]).toString("hex");
41
+ }
42
+ function satsToBtcNumber(value) {
43
+ return Number(value) / 100_000_000;
44
+ }
45
+ function valueToSats(value) {
46
+ const text = typeof value === "number" ? value.toFixed(8) : value;
47
+ const match = /^(-?)(\d+)(?:\.(\d{0,8}))?$/.exec(text.trim());
48
+ if (match == null) {
49
+ throw new Error(`wallet_cog_invalid_amount_${text}`);
50
+ }
51
+ const sign = match[1] === "-" ? -1n : 1n;
52
+ const whole = BigInt(match[2] ?? "0");
53
+ const fraction = BigInt((match[3] ?? "").padEnd(8, "0"));
54
+ return sign * ((whole * 100000000n) + fraction);
55
+ }
56
+ function createIntentFingerprint(parts) {
57
+ return createHash("sha256")
58
+ .update(parts.map((part) => String(part)).join("\n"))
59
+ .digest("hex");
60
+ }
61
+ function parseHex32(value, errorCode) {
62
+ const normalized = value.trim().toLowerCase();
63
+ if (!/^[0-9a-f]{64}$/.test(normalized)) {
64
+ throw new Error(errorCode);
65
+ }
66
+ return Buffer.from(normalized, "hex");
67
+ }
68
+ function sha256Hex(value) {
69
+ return createHash("sha256").update(value).digest("hex");
70
+ }
71
+ function resolveAnchorOutpointForSender(state, sender, errorPrefix) {
72
+ const anchoredDomains = state.domains.filter((domain) => domain.currentOwnerLocalIndex === sender.index
73
+ && domain.canonicalChainStatus === "anchored");
74
+ if (anchoredDomains.length === 0) {
75
+ return null;
76
+ }
77
+ const anchoredDomain = anchoredDomains[0];
78
+ if (anchoredDomain.currentCanonicalAnchorOutpoint === null) {
79
+ throw new Error(`${errorPrefix}_anchor_outpoint_unavailable`);
80
+ }
81
+ return {
82
+ txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
83
+ vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
84
+ };
85
+ }
86
+ function ensureUsableSender(sender, errorPrefix, amountCogtoshi) {
87
+ if (sender.address === null) {
88
+ throw new Error(`${errorPrefix}_sender_address_unavailable`);
89
+ }
90
+ if (sender.readOnly) {
91
+ throw new Error(`${errorPrefix}_sender_read_only`);
92
+ }
93
+ if (sender.observedCogBalance === null || sender.observedCogBalance < amountCogtoshi) {
94
+ throw new Error(`${errorPrefix}_insufficient_cog_balance`);
95
+ }
96
+ }
97
+ function createResolvedSenderSummary(identity) {
98
+ return {
99
+ selector: getCanonicalIdentitySelector(identity),
100
+ localIndex: identity.index,
101
+ scriptPubKeyHex: identity.scriptPubKeyHex,
102
+ address: identity.address,
103
+ };
104
+ }
105
+ function resolveIdentitySender(context, errorPrefix, amountCogtoshi, selector) {
106
+ assertWalletMutationContextReady(context, errorPrefix);
107
+ const identity = selector == null
108
+ ? (() => {
109
+ const eligible = context.model.identities.filter((candidate) => candidate.address !== null
110
+ && !candidate.readOnly
111
+ && candidate.observedCogBalance !== null
112
+ && candidate.observedCogBalance >= amountCogtoshi);
113
+ if (eligible.length === 0) {
114
+ throw new Error(`${errorPrefix}_no_eligible_sender`);
115
+ }
116
+ if (eligible.length > 1) {
117
+ throw new Error(`${errorPrefix}_ambiguous_sender`);
118
+ }
119
+ return eligible[0];
120
+ })()
121
+ : resolveIdentityBySelector(context, selector, errorPrefix);
122
+ ensureUsableSender(identity, errorPrefix, amountCogtoshi);
123
+ return {
124
+ state: context.localState.state,
125
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
126
+ sender: {
127
+ localIndex: identity.index,
128
+ scriptPubKeyHex: identity.scriptPubKeyHex,
129
+ address: identity.address,
130
+ },
131
+ anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, identity, errorPrefix),
132
+ resolved: {
133
+ sender: createResolvedSenderSummary(identity),
134
+ claimPath: null,
135
+ },
136
+ };
137
+ }
138
+ function resolveClaimSender(context, lockId, preimageHex, reclaim) {
139
+ const errorPrefix = reclaim ? "wallet_reclaim" : "wallet_claim";
140
+ assertWalletMutationContextReady(context, errorPrefix);
141
+ const currentHeight = context.snapshot.state.history.currentHeight;
142
+ if (currentHeight === null) {
143
+ throw new Error(`${errorPrefix}_current_height_unavailable`);
144
+ }
145
+ const lock = getLock(context.snapshot.state, lockId);
146
+ if (lock === null || lock.status !== "active") {
147
+ throw new Error(`${errorPrefix}_lock_not_found`);
148
+ }
149
+ const recipientDomain = lookupDomain(context.snapshot.state, context.model.domains.find((domain) => domain.domainId === lock.recipientDomainId)?.name ?? "")
150
+ ?? [...context.snapshot.state.consensus.domainsById.values()].find((entry) => entry.domainId === lock.recipientDomainId)
151
+ ?? null;
152
+ const recipientDomainName = recipientDomain?.name ?? null;
153
+ if (reclaim) {
154
+ if (currentHeight < lock.timeoutHeight) {
155
+ throw new Error("wallet_reclaim_before_timeout");
156
+ }
157
+ const lockerHex = Buffer.from(lock.lockerScriptPubKey).toString("hex");
158
+ const senderIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === lockerHex) ?? null;
159
+ if (senderIdentity === null) {
160
+ throw new Error("wallet_reclaim_sender_not_local");
161
+ }
162
+ ensureUsableSender(senderIdentity, errorPrefix, 0n);
163
+ return {
164
+ state: context.localState.state,
165
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
166
+ sender: {
167
+ localIndex: senderIdentity.index,
168
+ scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
169
+ address: senderIdentity.address,
170
+ },
171
+ anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, senderIdentity, errorPrefix),
172
+ recipientDomainName,
173
+ amountCogtoshi: lock.amount,
174
+ lockId: lock.lockId,
175
+ resolved: {
176
+ sender: createResolvedSenderSummary(senderIdentity),
177
+ claimPath: "timeout-reclaim",
178
+ },
179
+ };
180
+ }
181
+ if (currentHeight >= lock.timeoutHeight) {
182
+ throw new Error("wallet_claim_lock_expired");
183
+ }
184
+ const preimage = parseHex32(preimageHex, "wallet_claim_invalid_preimage");
185
+ if (sha256Hex(preimage) !== Buffer.from(lock.condition).toString("hex")) {
186
+ throw new Error("wallet_claim_preimage_mismatch");
187
+ }
188
+ if (recipientDomain === null) {
189
+ throw new Error("wallet_claim_recipient_domain_missing");
190
+ }
191
+ const recipientOwnerHex = Buffer.from(recipientDomain.ownerScriptPubKey).toString("hex");
192
+ const senderIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === recipientOwnerHex) ?? null;
193
+ if (senderIdentity === null) {
194
+ throw new Error("wallet_claim_sender_not_local");
195
+ }
196
+ ensureUsableSender(senderIdentity, errorPrefix, 0n);
197
+ return {
198
+ state: context.localState.state,
199
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
200
+ sender: {
201
+ localIndex: senderIdentity.index,
202
+ scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
203
+ address: senderIdentity.address,
204
+ },
205
+ anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, senderIdentity, errorPrefix),
206
+ recipientDomainName,
207
+ amountCogtoshi: lock.amount,
208
+ lockId: lock.lockId,
209
+ resolved: {
210
+ sender: createResolvedSenderSummary(senderIdentity),
211
+ claimPath: "recipient-claim",
212
+ },
213
+ };
214
+ }
215
+ function parseTimeoutHeight(currentHeight, rawRelative, rawAbsolute) {
216
+ if ((rawRelative == null) === (rawAbsolute == null)) {
217
+ throw new Error("wallet_lock_timeout_requires_exactly_one_mode");
218
+ }
219
+ if (rawAbsolute != null) {
220
+ if (!Number.isInteger(rawAbsolute)) {
221
+ throw new Error("wallet_lock_invalid_timeout_height");
222
+ }
223
+ return rawAbsolute;
224
+ }
225
+ const trimmed = rawRelative.trim().toLowerCase();
226
+ let blocks;
227
+ if (/^[1-9]\d*$/.test(trimmed)) {
228
+ blocks = Number.parseInt(trimmed, 10);
229
+ }
230
+ else {
231
+ const match = /^(\d+)(m|h|d|w)$/.exec(trimmed);
232
+ if (match == null) {
233
+ throw new Error("wallet_lock_invalid_timeout_duration");
234
+ }
235
+ const value = Number.parseInt(match[1], 10);
236
+ const minutesPerUnit = match[2] === "m" ? 1
237
+ : match[2] === "h" ? 60
238
+ : match[2] === "d" ? 24 * 60
239
+ : 7 * 24 * 60;
240
+ blocks = Math.ceil((value * minutesPerUnit) / 10);
241
+ }
242
+ return currentHeight + blocks;
243
+ }
244
+ function buildPlanForCogOperation(options) {
245
+ const fundingUtxos = options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
246
+ && entry.confirmations >= 1
247
+ && entry.spendable !== false
248
+ && entry.safe !== false);
249
+ const outputs = [{ data: Buffer.from(options.opReturnData).toString("hex") }];
250
+ if (options.anchorOutpoint === null) {
251
+ const senderUtxo = options.allUtxos.find((entry) => entry.scriptPubKey === options.sender.scriptPubKeyHex
252
+ && entry.confirmations >= 1
253
+ && entry.spendable !== false
254
+ && entry.safe !== false);
255
+ if (senderUtxo === undefined) {
256
+ throw new Error(`${options.errorPrefix}_sender_utxo_unavailable`);
257
+ }
258
+ return {
259
+ sender: options.sender,
260
+ changeAddress: options.state.funding.address,
261
+ inputs: [
262
+ { txid: senderUtxo.txid, vout: senderUtxo.vout },
263
+ ...fundingUtxos
264
+ .filter((entry) => !(entry.txid === senderUtxo.txid && entry.vout === senderUtxo.vout))
265
+ .map((entry) => ({ txid: entry.txid, vout: entry.vout })),
266
+ ],
267
+ outputs,
268
+ changePosition: 1,
269
+ expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
270
+ expectedAnchorScriptHex: null,
271
+ expectedAnchorValueSats: null,
272
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
273
+ errorPrefix: options.errorPrefix,
274
+ };
275
+ }
276
+ const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint?.txid
277
+ && entry.vout === options.anchorOutpoint.vout
278
+ && entry.scriptPubKey === options.sender.scriptPubKeyHex
279
+ && entry.confirmations >= 1
280
+ && entry.spendable !== false
281
+ && entry.safe !== false);
282
+ if (anchorUtxo === undefined) {
283
+ throw new Error(`${options.errorPrefix}_anchor_utxo_missing`);
284
+ }
285
+ outputs.push({
286
+ [options.sender.address]: satsToBtcNumber(options.anchorValueSats),
287
+ });
288
+ return {
289
+ sender: options.sender,
290
+ changeAddress: options.state.funding.address,
291
+ inputs: [
292
+ { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
293
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
294
+ ],
295
+ outputs,
296
+ changePosition: 2,
297
+ expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
298
+ expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
299
+ expectedAnchorValueSats: options.anchorValueSats,
300
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
301
+ errorPrefix: options.errorPrefix,
302
+ };
303
+ }
304
+ function validateFundedDraft(decoded, funded, plan) {
305
+ const inputs = decoded.tx.vin;
306
+ const outputs = decoded.tx.vout;
307
+ if (inputs.length === 0) {
308
+ throw new Error(`${plan.errorPrefix}_missing_sender_input`);
309
+ }
310
+ if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
311
+ throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
312
+ }
313
+ for (let index = 1; index < inputs.length; index += 1) {
314
+ if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
315
+ throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
316
+ }
317
+ }
318
+ if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
319
+ throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
320
+ }
321
+ if (plan.expectedAnchorScriptHex !== null) {
322
+ if (outputs[1]?.scriptPubKey?.hex !== plan.expectedAnchorScriptHex) {
323
+ throw new Error(`${plan.errorPrefix}_anchor_output_mismatch`);
324
+ }
325
+ if (valueToSats(outputs[1]?.value ?? 0) !== (plan.expectedAnchorValueSats ?? 0n)) {
326
+ throw new Error(`${plan.errorPrefix}_anchor_value_mismatch`);
327
+ }
328
+ }
329
+ const expectedWithoutChange = plan.expectedAnchorScriptHex === null ? 1 : 2;
330
+ if (funded.changepos === -1) {
331
+ if (outputs.length !== expectedWithoutChange) {
332
+ throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
333
+ }
334
+ return;
335
+ }
336
+ if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
337
+ throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
338
+ }
339
+ if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
340
+ throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
341
+ }
342
+ }
343
+ async function buildTransaction(options) {
344
+ return buildWalletMutationTransaction({
345
+ rpc: options.rpc,
346
+ walletName: options.walletName,
347
+ plan: options.plan,
348
+ validateFundedDraft,
349
+ finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
350
+ mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
351
+ });
352
+ }
353
+ function createDraftMutation(options) {
354
+ if (options.existing !== null && options.existing !== undefined) {
355
+ return {
356
+ ...options.existing,
357
+ kind: options.kind,
358
+ domainName: options.domainName ?? "",
359
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
360
+ senderLocalIndex: options.sender.localIndex,
361
+ recipientScriptPubKeyHex: options.recipientScriptPubKeyHex ?? null,
362
+ recipientDomainName: options.recipientDomainName ?? null,
363
+ amountCogtoshi: options.amountCogtoshi ?? null,
364
+ timeoutHeight: options.timeoutHeight ?? null,
365
+ conditionHex: options.conditionHex ?? null,
366
+ lockId: options.lockId ?? null,
367
+ preimageHex: options.preimageHex ?? null,
368
+ status: "draft",
369
+ lastUpdatedAtUnixMs: options.nowUnixMs,
370
+ attemptedTxid: null,
371
+ attemptedWtxid: null,
372
+ temporaryBuilderLockedOutpoints: [],
373
+ };
374
+ }
375
+ return {
376
+ mutationId: randomBytes(12).toString("hex"),
377
+ kind: options.kind,
378
+ domainName: options.domainName ?? "",
379
+ parentDomainName: null,
380
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
381
+ senderLocalIndex: options.sender.localIndex,
382
+ recipientScriptPubKeyHex: options.recipientScriptPubKeyHex ?? null,
383
+ recipientDomainName: options.recipientDomainName ?? null,
384
+ amountCogtoshi: options.amountCogtoshi ?? null,
385
+ timeoutHeight: options.timeoutHeight ?? null,
386
+ conditionHex: options.conditionHex ?? null,
387
+ lockId: options.lockId ?? null,
388
+ preimageHex: options.preimageHex ?? null,
389
+ intentFingerprintHex: options.intentFingerprintHex,
390
+ status: "draft",
391
+ createdAtUnixMs: options.nowUnixMs,
392
+ lastUpdatedAtUnixMs: options.nowUnixMs,
393
+ attemptedTxid: null,
394
+ attemptedWtxid: null,
395
+ temporaryBuilderLockedOutpoints: [],
396
+ };
397
+ }
398
+ async function reconcilePendingCogMutation(options) {
399
+ if (options.mutation.status === "confirmed" || options.mutation.status === "live") {
400
+ return {
401
+ state: options.state,
402
+ mutation: options.mutation,
403
+ resolution: options.mutation.status,
404
+ };
405
+ }
406
+ if (options.mutation.status === "repair-required") {
407
+ return {
408
+ state: options.state,
409
+ mutation: options.mutation,
410
+ resolution: "repair-required",
411
+ };
412
+ }
413
+ if (options.mutation.kind === "claim" && options.context.snapshot !== null && options.mutation.lockId != null) {
414
+ const lock = getLock(options.context.snapshot.state, options.mutation.lockId);
415
+ const expectedStatus = options.mutation.preimageHex === ZERO_PREIMAGE_HEX ? "reclaimed" : "claimed";
416
+ if (lock !== null
417
+ && lock.status === expectedStatus
418
+ && Buffer.from(lock.resolverScriptPubKey ?? new Uint8Array()).toString("hex") === options.mutation.senderScriptPubKeyHex) {
419
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
420
+ const confirmed = updateMutationRecord(options.mutation, "confirmed", options.nowUnixMs, {
421
+ temporaryBuilderLockedOutpoints: [],
422
+ });
423
+ const nextState = {
424
+ ...upsertPendingMutation(options.state, confirmed),
425
+ stateRevision: options.state.stateRevision + 1,
426
+ lastWrittenAtUnixMs: options.nowUnixMs,
427
+ };
428
+ await saveWalletStatePreservingUnlock({
429
+ state: nextState,
430
+ provider: options.provider,
431
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
432
+ nowUnixMs: options.nowUnixMs,
433
+ paths: options.paths,
434
+ });
435
+ return { state: nextState, mutation: confirmed, resolution: "confirmed" };
436
+ }
437
+ }
438
+ const known = options.mutation.attemptedTxid === null
439
+ ? false
440
+ : await options.rpc.getRawTransaction(options.mutation.attemptedTxid, true).then(() => true).catch(() => false);
441
+ if (known) {
442
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
443
+ const live = updateMutationRecord(options.mutation, "live", options.nowUnixMs, {
444
+ temporaryBuilderLockedOutpoints: [],
445
+ });
446
+ const nextState = {
447
+ ...upsertPendingMutation(options.state, live),
448
+ stateRevision: options.state.stateRevision + 1,
449
+ lastWrittenAtUnixMs: options.nowUnixMs,
450
+ };
451
+ await saveWalletStatePreservingUnlock({
452
+ state: nextState,
453
+ provider: options.provider,
454
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
455
+ nowUnixMs: options.nowUnixMs,
456
+ paths: options.paths,
457
+ });
458
+ return { state: nextState, mutation: live, resolution: "live" };
459
+ }
460
+ return { state: options.state, mutation: options.mutation, resolution: "continue" };
461
+ }
462
+ async function confirmSend(prompter, resolved, target, normalizedRecipient, amountCogtoshi, assumeYes = false) {
463
+ prompter.writeLine(`You are sending ${formatCogAmount(amountCogtoshi)}.`);
464
+ prompter.writeLine(`Resolved sender: ${resolved.sender.selector} (${resolved.sender.address})`);
465
+ prompter.writeLine(`Recipient: ${normalizedRecipient.address ?? `spk:${normalizedRecipient.scriptPubKeyHex}`}`);
466
+ if (normalizedRecipient.opaque) {
467
+ await confirmTypedAcknowledgement(prompter, {
468
+ assumeYes,
469
+ expected: target.trim(),
470
+ prompt: "Type the exact target to continue: ",
471
+ errorCode: "wallet_send_confirmation_rejected",
472
+ requiresTtyErrorCode: "wallet_send_requires_tty",
473
+ typedAckRequiredErrorCode: "wallet_send_typed_ack_required",
474
+ });
475
+ return;
476
+ }
477
+ await confirmYesNo(prompter, "This will publish an on-chain COG transfer.", {
478
+ assumeYes,
479
+ errorCode: "wallet_send_confirmation_rejected",
480
+ requiresTtyErrorCode: "wallet_send_requires_tty",
481
+ });
482
+ }
483
+ async function confirmLock(prompter, resolved, amountCogtoshi, recipientDomainName, timeoutHeight, assumeYes = false) {
484
+ prompter.writeLine(`You are locking ${formatCogAmount(amountCogtoshi)}.`);
485
+ prompter.writeLine(`Resolved sender: ${resolved.sender.selector} (${resolved.sender.address})`);
486
+ prompter.writeLine(`Recipient domain: ${recipientDomainName}`);
487
+ prompter.writeLine(`Resolved timeout height: ${timeoutHeight}`);
488
+ await confirmYesNo(prompter, "This creates an escrowed COG lock and the funds cannot be spent until claimed or reclaimed.", {
489
+ assumeYes,
490
+ errorCode: "wallet_mutation_confirmation_rejected",
491
+ requiresTtyErrorCode: "wallet_lock_requires_tty",
492
+ });
493
+ }
494
+ async function confirmClaim(prompter, options) {
495
+ prompter.writeLine(`${options.kind === "claim" ? "Claiming" : "Reclaiming"} lock:${options.lockId} for ${formatCogAmount(options.amountCogtoshi)}.`);
496
+ prompter.writeLine(`Resolved sender: ${options.resolved.sender.selector} (${options.resolved.sender.address})`);
497
+ if (options.resolved.claimPath !== null) {
498
+ prompter.writeLine(`Resolved path: ${options.resolved.claimPath}.`);
499
+ }
500
+ if (options.recipientDomainName !== null) {
501
+ prompter.writeLine(`Recipient domain: ${options.recipientDomainName}`);
502
+ }
503
+ if (options.kind === "claim") {
504
+ prompter.writeLine("Warning: the claim preimage becomes public in the mempool and on-chain.");
505
+ }
506
+ await confirmYesNo(prompter, options.kind === "claim"
507
+ ? "This spends the lock via the recipient claim path."
508
+ : "This spends the lock via the timeout reclaim path.", {
509
+ assumeYes: options.assumeYes,
510
+ errorCode: options.kind === "claim"
511
+ ? "wallet_claim_confirmation_rejected"
512
+ : "wallet_reclaim_confirmation_rejected",
513
+ requiresTtyErrorCode: options.kind === "claim"
514
+ ? "wallet_claim_requires_tty"
515
+ : "wallet_reclaim_requires_tty",
516
+ });
517
+ }
518
+ async function sendBuiltTransaction(options) {
519
+ let nextState = options.state;
520
+ const broadcasting = updateMutationRecord(options.mutation, "broadcasting", options.nowUnixMs, {
521
+ attemptedTxid: options.built.txid,
522
+ attemptedWtxid: options.built.wtxid,
523
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
524
+ });
525
+ nextState = {
526
+ ...upsertPendingMutation(nextState, broadcasting),
527
+ stateRevision: nextState.stateRevision + 1,
528
+ lastWrittenAtUnixMs: options.nowUnixMs,
529
+ };
530
+ await saveWalletStatePreservingUnlock({
531
+ state: nextState,
532
+ provider: options.provider,
533
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
534
+ nowUnixMs: options.nowUnixMs,
535
+ paths: options.paths,
536
+ });
537
+ if (options.snapshotHeight !== null && options.snapshotHeight !== (await options.rpc.getBlockchainInfo()).blocks) {
538
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
539
+ throw new Error(`${options.errorPrefix}_tip_mismatch`);
540
+ }
541
+ try {
542
+ await options.rpc.sendRawTransaction(options.built.rawHex);
543
+ }
544
+ catch (error) {
545
+ if (!isAlreadyAcceptedError(error)) {
546
+ if (isBroadcastUnknownError(error)) {
547
+ const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", options.nowUnixMs, {
548
+ attemptedTxid: options.built.txid,
549
+ attemptedWtxid: options.built.wtxid,
550
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
551
+ });
552
+ nextState = {
553
+ ...upsertPendingMutation(nextState, unknown),
554
+ stateRevision: nextState.stateRevision + 1,
555
+ lastWrittenAtUnixMs: options.nowUnixMs,
556
+ };
557
+ await saveWalletStatePreservingUnlock({
558
+ state: nextState,
559
+ provider: options.provider,
560
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
561
+ nowUnixMs: options.nowUnixMs,
562
+ paths: options.paths,
563
+ });
564
+ throw new Error(`${options.errorPrefix}_broadcast_unknown`);
565
+ }
566
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
567
+ const canceled = updateMutationRecord(broadcasting, "canceled", options.nowUnixMs, {
568
+ attemptedTxid: options.built.txid,
569
+ attemptedWtxid: options.built.wtxid,
570
+ temporaryBuilderLockedOutpoints: [],
571
+ });
572
+ nextState = {
573
+ ...upsertPendingMutation(nextState, canceled),
574
+ stateRevision: nextState.stateRevision + 1,
575
+ lastWrittenAtUnixMs: options.nowUnixMs,
576
+ };
577
+ await saveWalletStatePreservingUnlock({
578
+ state: nextState,
579
+ provider: options.provider,
580
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
581
+ nowUnixMs: options.nowUnixMs,
582
+ paths: options.paths,
583
+ });
584
+ throw error;
585
+ }
586
+ }
587
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
588
+ const live = updateMutationRecord(broadcasting, "live", options.nowUnixMs, {
589
+ attemptedTxid: options.built.txid,
590
+ attemptedWtxid: options.built.wtxid,
591
+ temporaryBuilderLockedOutpoints: [],
592
+ });
593
+ nextState = {
594
+ ...upsertPendingMutation(nextState, live),
595
+ stateRevision: nextState.stateRevision + 1,
596
+ lastWrittenAtUnixMs: options.nowUnixMs,
597
+ };
598
+ await saveWalletStatePreservingUnlock({
599
+ state: nextState,
600
+ provider: options.provider,
601
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
602
+ nowUnixMs: options.nowUnixMs,
603
+ paths: options.paths,
604
+ });
605
+ return { state: nextState, mutation: live };
606
+ }
607
+ export async function sendCog(options) {
608
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
609
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
610
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
611
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
612
+ purpose: "wallet-send",
613
+ walletRootId: null,
614
+ });
615
+ const amountCogtoshi = normalizePositiveAmount(options.amountCogtoshi, "wallet_send_invalid_amount");
616
+ const recipient = normalizeBtcTarget(options.target);
617
+ try {
618
+ const miningPreemption = await pauseMiningForWalletMutation({
619
+ paths,
620
+ reason: "wallet-send",
621
+ });
622
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
623
+ dataDir: options.dataDir,
624
+ databasePath: options.databasePath,
625
+ secretProvider: provider,
626
+ paths,
627
+ });
628
+ try {
629
+ const operation = resolveIdentitySender(readContext, "wallet_send", amountCogtoshi, options.fromIdentity);
630
+ if (operation.sender.scriptPubKeyHex === recipient.scriptPubKeyHex) {
631
+ throw new Error("wallet_send_self_transfer");
632
+ }
633
+ const intentFingerprintHex = createIntentFingerprint([
634
+ "send",
635
+ operation.state.walletRootId,
636
+ operation.sender.scriptPubKeyHex,
637
+ recipient.scriptPubKeyHex,
638
+ amountCogtoshi,
639
+ ]);
640
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
641
+ dataDir: options.dataDir,
642
+ chain: "main",
643
+ startHeight: 0,
644
+ walletRootId: operation.state.walletRootId,
645
+ });
646
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
647
+ const walletName = operation.state.managedCoreWallet.walletName;
648
+ const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
649
+ if (existingMutation !== null) {
650
+ const reconciled = await reconcilePendingCogMutation({
651
+ state: operation.state,
652
+ mutation: existingMutation,
653
+ provider,
654
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
655
+ nowUnixMs,
656
+ paths,
657
+ rpc,
658
+ walletName,
659
+ context: readContext,
660
+ });
661
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
662
+ return {
663
+ kind: "send",
664
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
665
+ status: reconciled.resolution,
666
+ reusedExisting: true,
667
+ amountCogtoshi,
668
+ recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
669
+ resolved: operation.resolved,
670
+ };
671
+ }
672
+ if (reconciled.resolution === "repair-required") {
673
+ throw new Error("wallet_send_repair_required");
674
+ }
675
+ }
676
+ await confirmSend(options.prompter, operation.resolved, options.target, recipient, amountCogtoshi, options.assumeYes);
677
+ let nextState = upsertPendingMutation(operation.state, createDraftMutation({
678
+ kind: "send",
679
+ sender: operation.sender,
680
+ recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
681
+ amountCogtoshi,
682
+ intentFingerprintHex,
683
+ nowUnixMs,
684
+ existing: existingMutation,
685
+ }));
686
+ nextState = {
687
+ ...nextState,
688
+ stateRevision: nextState.stateRevision + 1,
689
+ lastWrittenAtUnixMs: nowUnixMs,
690
+ };
691
+ await saveWalletStatePreservingUnlock({
692
+ state: nextState,
693
+ provider,
694
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
695
+ nowUnixMs,
696
+ paths,
697
+ });
698
+ const built = await buildTransaction({
699
+ rpc,
700
+ walletName,
701
+ plan: buildPlanForCogOperation({
702
+ state: nextState,
703
+ allUtxos: await rpc.listUnspent(walletName, 1),
704
+ sender: operation.sender,
705
+ anchorOutpoint: operation.anchorOutpoint,
706
+ opReturnData: serializeCogTransfer(amountCogtoshi, Buffer.from(recipient.scriptPubKeyHex, "hex")).opReturnData,
707
+ anchorValueSats: BigInt(nextState.anchorValueSats),
708
+ errorPrefix: "wallet_send",
709
+ }),
710
+ });
711
+ const final = await sendBuiltTransaction({
712
+ rpc,
713
+ walletName,
714
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
715
+ built,
716
+ mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
717
+ state: nextState,
718
+ provider,
719
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
720
+ nowUnixMs,
721
+ paths,
722
+ errorPrefix: "wallet_send",
723
+ });
724
+ return {
725
+ kind: "send",
726
+ txid: final.mutation.attemptedTxid ?? built.txid,
727
+ status: "live",
728
+ reusedExisting: false,
729
+ amountCogtoshi,
730
+ recipientScriptPubKeyHex: recipient.scriptPubKeyHex,
731
+ resolved: operation.resolved,
732
+ };
733
+ }
734
+ finally {
735
+ await readContext.close();
736
+ await miningPreemption.release();
737
+ }
738
+ }
739
+ finally {
740
+ await controlLock.release();
741
+ }
742
+ }
743
+ export async function lockCogToDomain(options) {
744
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
745
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
746
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
747
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
748
+ purpose: "wallet-lock-cog",
749
+ walletRootId: null,
750
+ });
751
+ const amountCogtoshi = normalizePositiveAmount(options.amountCogtoshi, "wallet_lock_invalid_amount");
752
+ const normalizedRecipientDomainName = normalizeDomainName(options.recipientDomainName);
753
+ const condition = parseHex32(options.conditionHex, "wallet_lock_invalid_condition");
754
+ if (condition.equals(Buffer.alloc(32))) {
755
+ throw new Error("wallet_lock_invalid_condition");
756
+ }
757
+ try {
758
+ const miningPreemption = await pauseMiningForWalletMutation({
759
+ paths,
760
+ reason: "wallet-cog-lock",
761
+ });
762
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
763
+ dataDir: options.dataDir,
764
+ databasePath: options.databasePath,
765
+ secretProvider: provider,
766
+ paths,
767
+ });
768
+ try {
769
+ assertWalletMutationContextReady(readContext, "wallet_lock");
770
+ const currentHeight = readContext.snapshot.state.history.currentHeight;
771
+ if (currentHeight === null) {
772
+ throw new Error("wallet_lock_current_height_unavailable");
773
+ }
774
+ const timeoutHeight = parseTimeoutHeight(currentHeight, options.timeoutBlocksOrDuration, options.timeoutHeight ?? null);
775
+ if (timeoutHeight <= currentHeight || timeoutHeight > currentHeight + MAX_LOCK_DURATION_BLOCKS) {
776
+ throw new Error("wallet_lock_invalid_timeout_height");
777
+ }
778
+ const recipientDomain = lookupDomain(readContext.snapshot.state, normalizedRecipientDomainName);
779
+ if (recipientDomain === null) {
780
+ throw new Error("wallet_lock_domain_not_found");
781
+ }
782
+ if (!recipientDomain.anchored) {
783
+ throw new Error("wallet_lock_domain_not_anchored");
784
+ }
785
+ if (readContext.snapshot.state.consensus.nextLockId === 0xffff_ffff) {
786
+ throw new Error("wallet_lock_id_space_exhausted");
787
+ }
788
+ const operation = resolveIdentitySender(readContext, "wallet_lock", amountCogtoshi, options.fromIdentity);
789
+ const intentFingerprintHex = createIntentFingerprint([
790
+ "lock",
791
+ operation.state.walletRootId,
792
+ operation.sender.scriptPubKeyHex,
793
+ normalizedRecipientDomainName,
794
+ amountCogtoshi,
795
+ timeoutHeight,
796
+ Buffer.from(condition).toString("hex"),
797
+ ]);
798
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
799
+ dataDir: options.dataDir,
800
+ chain: "main",
801
+ startHeight: 0,
802
+ walletRootId: operation.state.walletRootId,
803
+ });
804
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
805
+ const walletName = operation.state.managedCoreWallet.walletName;
806
+ const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
807
+ if (existingMutation !== null) {
808
+ const reconciled = await reconcilePendingCogMutation({
809
+ state: operation.state,
810
+ mutation: existingMutation,
811
+ provider,
812
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
813
+ nowUnixMs,
814
+ paths,
815
+ rpc,
816
+ walletName,
817
+ context: readContext,
818
+ });
819
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
820
+ return {
821
+ kind: "lock",
822
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
823
+ status: reconciled.resolution,
824
+ reusedExisting: true,
825
+ amountCogtoshi,
826
+ recipientDomainName: normalizedRecipientDomainName,
827
+ resolved: operation.resolved,
828
+ };
829
+ }
830
+ if (reconciled.resolution === "repair-required") {
831
+ throw new Error("wallet_lock_repair_required");
832
+ }
833
+ }
834
+ await confirmLock(options.prompter, operation.resolved, amountCogtoshi, normalizedRecipientDomainName, timeoutHeight, options.assumeYes);
835
+ let nextState = upsertPendingMutation(operation.state, createDraftMutation({
836
+ kind: "lock",
837
+ sender: operation.sender,
838
+ amountCogtoshi,
839
+ recipientDomainName: normalizedRecipientDomainName,
840
+ timeoutHeight,
841
+ conditionHex: Buffer.from(condition).toString("hex"),
842
+ intentFingerprintHex,
843
+ nowUnixMs,
844
+ existing: existingMutation,
845
+ }));
846
+ nextState = {
847
+ ...nextState,
848
+ stateRevision: nextState.stateRevision + 1,
849
+ lastWrittenAtUnixMs: nowUnixMs,
850
+ };
851
+ await saveWalletStatePreservingUnlock({
852
+ state: nextState,
853
+ provider,
854
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
855
+ nowUnixMs,
856
+ paths,
857
+ });
858
+ const built = await buildTransaction({
859
+ rpc,
860
+ walletName,
861
+ plan: buildPlanForCogOperation({
862
+ state: nextState,
863
+ allUtxos: await rpc.listUnspent(walletName, 1),
864
+ sender: operation.sender,
865
+ anchorOutpoint: operation.anchorOutpoint,
866
+ opReturnData: serializeCogLock(amountCogtoshi, timeoutHeight, recipientDomain.domainId, condition).opReturnData,
867
+ anchorValueSats: BigInt(nextState.anchorValueSats),
868
+ errorPrefix: "wallet_lock",
869
+ }),
870
+ });
871
+ const final = await sendBuiltTransaction({
872
+ rpc,
873
+ walletName,
874
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
875
+ built,
876
+ mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
877
+ state: nextState,
878
+ provider,
879
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
880
+ nowUnixMs,
881
+ paths,
882
+ errorPrefix: "wallet_lock",
883
+ });
884
+ return {
885
+ kind: "lock",
886
+ txid: final.mutation.attemptedTxid ?? built.txid,
887
+ status: "live",
888
+ reusedExisting: false,
889
+ amountCogtoshi,
890
+ recipientDomainName: normalizedRecipientDomainName,
891
+ resolved: operation.resolved,
892
+ };
893
+ }
894
+ finally {
895
+ await readContext.close();
896
+ await miningPreemption.release();
897
+ }
898
+ }
899
+ finally {
900
+ await controlLock.release();
901
+ }
902
+ }
903
+ async function runClaimLikeMutation(options, reclaim) {
904
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
905
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
906
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
907
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
908
+ purpose: reclaim ? "wallet-reclaim" : "wallet-claim",
909
+ walletRootId: null,
910
+ });
911
+ const preimageHex = reclaim ? ZERO_PREIMAGE_HEX : options.preimageHex;
912
+ try {
913
+ const miningPreemption = await pauseMiningForWalletMutation({
914
+ paths,
915
+ reason: reclaim ? "wallet-reclaim" : "wallet-claim",
916
+ });
917
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
918
+ dataDir: options.dataDir,
919
+ databasePath: options.databasePath,
920
+ secretProvider: provider,
921
+ paths,
922
+ });
923
+ try {
924
+ const operation = resolveClaimSender(readContext, options.lockId, preimageHex, reclaim);
925
+ const errorPrefix = reclaim ? "wallet_reclaim" : "wallet_claim";
926
+ const intentFingerprintHex = createIntentFingerprint([
927
+ reclaim ? "reclaim" : "claim",
928
+ operation.state.walletRootId,
929
+ operation.sender.scriptPubKeyHex,
930
+ operation.lockId,
931
+ preimageHex,
932
+ ]);
933
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
934
+ dataDir: options.dataDir,
935
+ chain: "main",
936
+ startHeight: 0,
937
+ walletRootId: operation.state.walletRootId,
938
+ });
939
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
940
+ const walletName = operation.state.managedCoreWallet.walletName;
941
+ const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
942
+ if (existingMutation !== null) {
943
+ const reconciled = await reconcilePendingCogMutation({
944
+ state: operation.state,
945
+ mutation: existingMutation,
946
+ provider,
947
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
948
+ nowUnixMs,
949
+ paths,
950
+ rpc,
951
+ walletName,
952
+ context: readContext,
953
+ });
954
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
955
+ return {
956
+ kind: "claim",
957
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
958
+ status: reconciled.resolution,
959
+ reusedExisting: true,
960
+ amountCogtoshi: operation.amountCogtoshi,
961
+ recipientDomainName: operation.recipientDomainName,
962
+ lockId: options.lockId,
963
+ resolved: operation.resolved,
964
+ };
965
+ }
966
+ if (reconciled.resolution === "repair-required") {
967
+ throw new Error(`${errorPrefix}_repair_required`);
968
+ }
969
+ }
970
+ await confirmClaim(options.prompter, {
971
+ kind: reclaim ? "reclaim" : "claim",
972
+ lockId: options.lockId,
973
+ recipientDomainName: operation.recipientDomainName,
974
+ amountCogtoshi: operation.amountCogtoshi,
975
+ resolved: operation.resolved,
976
+ assumeYes: options.assumeYes,
977
+ });
978
+ let nextState = upsertPendingMutation(operation.state, createDraftMutation({
979
+ kind: "claim",
980
+ sender: operation.sender,
981
+ amountCogtoshi: operation.amountCogtoshi,
982
+ recipientDomainName: operation.recipientDomainName,
983
+ lockId: options.lockId,
984
+ preimageHex,
985
+ intentFingerprintHex,
986
+ nowUnixMs,
987
+ existing: existingMutation,
988
+ }));
989
+ nextState = {
990
+ ...nextState,
991
+ stateRevision: nextState.stateRevision + 1,
992
+ lastWrittenAtUnixMs: nowUnixMs,
993
+ };
994
+ await saveWalletStatePreservingUnlock({
995
+ state: nextState,
996
+ provider,
997
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
998
+ nowUnixMs,
999
+ paths,
1000
+ });
1001
+ const built = await buildTransaction({
1002
+ rpc,
1003
+ walletName,
1004
+ plan: buildPlanForCogOperation({
1005
+ state: nextState,
1006
+ allUtxos: await rpc.listUnspent(walletName, 1),
1007
+ sender: operation.sender,
1008
+ anchorOutpoint: operation.anchorOutpoint,
1009
+ opReturnData: serializeCogClaim(options.lockId, Buffer.from(preimageHex, "hex")).opReturnData,
1010
+ anchorValueSats: BigInt(nextState.anchorValueSats),
1011
+ errorPrefix,
1012
+ }),
1013
+ });
1014
+ const final = await sendBuiltTransaction({
1015
+ rpc,
1016
+ walletName,
1017
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
1018
+ built,
1019
+ mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
1020
+ state: nextState,
1021
+ provider,
1022
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
1023
+ nowUnixMs,
1024
+ paths,
1025
+ errorPrefix,
1026
+ });
1027
+ return {
1028
+ kind: "claim",
1029
+ txid: final.mutation.attemptedTxid ?? built.txid,
1030
+ status: "live",
1031
+ reusedExisting: false,
1032
+ amountCogtoshi: operation.amountCogtoshi,
1033
+ recipientDomainName: operation.recipientDomainName,
1034
+ lockId: options.lockId,
1035
+ resolved: operation.resolved,
1036
+ };
1037
+ }
1038
+ finally {
1039
+ await readContext.close();
1040
+ await miningPreemption.release();
1041
+ }
1042
+ }
1043
+ finally {
1044
+ await controlLock.release();
1045
+ }
1046
+ }
1047
+ export async function claimCogLock(options) {
1048
+ return runClaimLikeMutation(options, false);
1049
+ }
1050
+ export async function reclaimCogLock(options) {
1051
+ return runClaimLikeMutation({
1052
+ ...options,
1053
+ preimageHex: ZERO_PREIMAGE_HEX,
1054
+ }, true);
1055
+ }