@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,693 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { encodeSentence } from "@cogcoin/scoring";
3
+ import { getBalance, lookupDomain, } from "@cogcoin/indexer/queries";
4
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
5
+ import { createRpcClient } from "../../bitcoind/node.js";
6
+ import { acquireFileLock } from "../fs/lock.js";
7
+ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
8
+ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
9
+ import { serializeRepCommit, serializeRepRevoke, validateDomainName, } from "../cogop/index.js";
10
+ import { openWalletReadContext } from "../read/index.js";
11
+ import { assertWalletMutationContextReady, buildWalletMutationTransaction, formatCogAmount, isAlreadyAcceptedError, isBroadcastUnknownError, pauseMiningForWalletMutation, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
12
+ import { confirmTypedAcknowledgement as confirmSharedTypedAcknowledgement, confirmYesNo as confirmSharedYesNo, } from "./confirm.js";
13
+ import { getCanonicalIdentitySelector } from "./identity-selector.js";
14
+ import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
15
+ function normalizeDomainName(domainName, errorCode) {
16
+ const normalized = domainName.trim().toLowerCase();
17
+ if (normalized.length === 0) {
18
+ throw new Error(errorCode);
19
+ }
20
+ validateDomainName(normalized);
21
+ return normalized;
22
+ }
23
+ function createSupportKey(sourceDomainId, targetDomainId) {
24
+ return `${sourceDomainId}:${targetDomainId}`;
25
+ }
26
+ function encodeOpReturnScript(payload) {
27
+ if (payload.length <= 75) {
28
+ return Buffer.concat([
29
+ Buffer.from([0x6a, payload.length]),
30
+ Buffer.from(payload),
31
+ ]).toString("hex");
32
+ }
33
+ return Buffer.concat([
34
+ Buffer.from([0x6a, 0x4c, payload.length]),
35
+ Buffer.from(payload),
36
+ ]).toString("hex");
37
+ }
38
+ function satsToBtcNumber(value) {
39
+ return Number(value) / 100_000_000;
40
+ }
41
+ function valueToSats(value) {
42
+ const text = typeof value === "number" ? value.toFixed(8) : value;
43
+ const match = /^(-?)(\d+)(?:\.(\d{0,8}))?$/.exec(text.trim());
44
+ if (match == null) {
45
+ throw new Error(`wallet_reputation_invalid_amount_${text}`);
46
+ }
47
+ const sign = match[1] === "-" ? -1n : 1n;
48
+ const whole = BigInt(match[2] ?? "0");
49
+ const fraction = BigInt((match[3] ?? "").padEnd(8, "0"));
50
+ return sign * ((whole * 100000000n) + fraction);
51
+ }
52
+ function createIntentFingerprint(parts) {
53
+ return createHash("sha256")
54
+ .update(parts.map((part) => String(part)).join("\n"))
55
+ .digest("hex");
56
+ }
57
+ function createResolvedReputationSenderSummary(sender, selector) {
58
+ return {
59
+ selector,
60
+ localIndex: sender.localIndex,
61
+ scriptPubKeyHex: sender.scriptPubKeyHex,
62
+ address: sender.address,
63
+ };
64
+ }
65
+ function createResolvedReputationSummary(options) {
66
+ return {
67
+ sender: createResolvedReputationSenderSummary(options.sender, options.senderSelector),
68
+ effect: options.kind === "give"
69
+ ? {
70
+ kind: "give-support",
71
+ burnCogtoshi: options.amountCogtoshi.toString(),
72
+ }
73
+ : {
74
+ kind: "revoke-support",
75
+ burnCogtoshi: options.amountCogtoshi.toString(),
76
+ },
77
+ review: {
78
+ included: options.review.payloadHex !== null,
79
+ byteLength: options.review.payload?.length ?? null,
80
+ },
81
+ selfStake: options.selfStake,
82
+ };
83
+ }
84
+ function describeReputationEffect(effect) {
85
+ if (effect.kind === "give-support") {
86
+ return `burn ${effect.burnCogtoshi} cogtoshi to publish support`;
87
+ }
88
+ return `revoke visible support with no refund of the previously burned ${effect.burnCogtoshi} cogtoshi`;
89
+ }
90
+ function describeReputationReview(review) {
91
+ if (!review.included || review.byteLength === null) {
92
+ return "none";
93
+ }
94
+ return `included (${review.byteLength} bytes)`;
95
+ }
96
+ function resolveAnchorOutpointForSender(state, sender, errorPrefix) {
97
+ const anchoredDomain = state.domains.find((domain) => domain.currentOwnerLocalIndex === sender.index
98
+ && domain.canonicalChainStatus === "anchored") ?? null;
99
+ if (anchoredDomain?.currentCanonicalAnchorOutpoint === null || anchoredDomain === null) {
100
+ throw new Error(`${errorPrefix}_anchor_outpoint_unavailable`);
101
+ }
102
+ return {
103
+ txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
104
+ vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
105
+ };
106
+ }
107
+ function resolveReputationOperation(context, sourceDomainName, targetDomainName, errorPrefix) {
108
+ assertWalletMutationContextReady(context, errorPrefix);
109
+ const sourceDomain = lookupDomain(context.snapshot.state, sourceDomainName);
110
+ if (sourceDomain === null) {
111
+ throw new Error(`${errorPrefix}_source_domain_not_found`);
112
+ }
113
+ if (!sourceDomain.anchored) {
114
+ throw new Error(`${errorPrefix}_source_domain_not_anchored`);
115
+ }
116
+ const targetDomain = lookupDomain(context.snapshot.state, targetDomainName);
117
+ if (targetDomain === null) {
118
+ throw new Error(`${errorPrefix}_target_domain_not_found`);
119
+ }
120
+ if (!targetDomain.anchored) {
121
+ throw new Error(`${errorPrefix}_target_domain_not_anchored`);
122
+ }
123
+ const ownerHex = Buffer.from(sourceDomain.ownerScriptPubKey).toString("hex");
124
+ const ownerIdentity = context.model.identities.find((identity) => identity.scriptPubKeyHex === ownerHex) ?? null;
125
+ if (ownerIdentity === null || ownerIdentity.address === null) {
126
+ throw new Error(`${errorPrefix}_source_owner_not_locally_controlled`);
127
+ }
128
+ if (ownerIdentity.readOnly) {
129
+ throw new Error(`${errorPrefix}_source_owner_read_only`);
130
+ }
131
+ return {
132
+ readContext: context,
133
+ state: context.localState.state,
134
+ unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
135
+ sender: {
136
+ localIndex: ownerIdentity.index,
137
+ scriptPubKeyHex: ownerIdentity.scriptPubKeyHex,
138
+ address: ownerIdentity.address,
139
+ },
140
+ senderSelector: getCanonicalIdentitySelector(ownerIdentity),
141
+ anchorOutpoint: resolveAnchorOutpointForSender(context.localState.state, ownerIdentity, errorPrefix),
142
+ sourceDomain,
143
+ targetDomain,
144
+ availableBalanceCogtoshi: getBalance(context.snapshot.state, sourceDomain.ownerScriptPubKey),
145
+ currentNetSupportCogtoshi: context.snapshot.state.consensus.supportByPair.get(createSupportKey(sourceDomain.domainId, targetDomain.domainId)) ?? 0n,
146
+ };
147
+ }
148
+ function buildPlanForReputationOperation(options) {
149
+ const fundingUtxos = options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
150
+ && entry.confirmations >= 1
151
+ && entry.spendable !== false
152
+ && entry.safe !== false);
153
+ const anchorUtxo = options.allUtxos.find((entry) => entry.txid === options.anchorOutpoint.txid
154
+ && entry.vout === options.anchorOutpoint.vout
155
+ && entry.scriptPubKey === options.sender.scriptPubKeyHex
156
+ && entry.confirmations >= 1
157
+ && entry.spendable !== false
158
+ && entry.safe !== false);
159
+ if (anchorUtxo === undefined) {
160
+ throw new Error(`${options.errorPrefix}_anchor_utxo_missing`);
161
+ }
162
+ return {
163
+ sender: options.sender,
164
+ changeAddress: options.state.funding.address,
165
+ inputs: [
166
+ { txid: anchorUtxo.txid, vout: anchorUtxo.vout },
167
+ ...fundingUtxos.map((entry) => ({ txid: entry.txid, vout: entry.vout })),
168
+ ],
169
+ outputs: [
170
+ { data: Buffer.from(options.opReturnData).toString("hex") },
171
+ { [options.sender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
172
+ ],
173
+ changePosition: 2,
174
+ expectedOpReturnScriptHex: encodeOpReturnScript(options.opReturnData),
175
+ expectedAnchorScriptHex: options.sender.scriptPubKeyHex,
176
+ expectedAnchorValueSats: BigInt(options.state.anchorValueSats),
177
+ allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
178
+ errorPrefix: options.errorPrefix,
179
+ };
180
+ }
181
+ function validateFundedDraft(decoded, funded, plan) {
182
+ const inputs = decoded.tx.vin;
183
+ const outputs = decoded.tx.vout;
184
+ if (inputs.length === 0) {
185
+ throw new Error(`${plan.errorPrefix}_missing_sender_input`);
186
+ }
187
+ if (inputs[0]?.prevout?.scriptPubKey?.hex !== plan.sender.scriptPubKeyHex) {
188
+ throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
189
+ }
190
+ for (let index = 1; index < inputs.length; index += 1) {
191
+ if (inputs[index]?.prevout?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
192
+ throw new Error(`${plan.errorPrefix}_unexpected_funding_input`);
193
+ }
194
+ }
195
+ if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
196
+ throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
197
+ }
198
+ if (outputs[1]?.scriptPubKey?.hex !== plan.expectedAnchorScriptHex) {
199
+ throw new Error(`${plan.errorPrefix}_anchor_output_mismatch`);
200
+ }
201
+ if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedAnchorValueSats) {
202
+ throw new Error(`${plan.errorPrefix}_anchor_value_mismatch`);
203
+ }
204
+ if (funded.changepos === -1) {
205
+ if (outputs.length !== 2) {
206
+ throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
207
+ }
208
+ return;
209
+ }
210
+ if (funded.changepos !== plan.changePosition || outputs.length !== 3) {
211
+ throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
212
+ }
213
+ if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
214
+ throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
215
+ }
216
+ }
217
+ async function buildTransaction(options) {
218
+ return buildWalletMutationTransaction({
219
+ rpc: options.rpc,
220
+ walletName: options.walletName,
221
+ plan: options.plan,
222
+ validateFundedDraft,
223
+ finalizeErrorCode: `${options.plan.errorPrefix}_finalize_failed`,
224
+ mempoolRejectPrefix: `${options.plan.errorPrefix}_mempool_rejected`,
225
+ });
226
+ }
227
+ function createDraftMutation(options) {
228
+ if (options.existing !== null && options.existing !== undefined) {
229
+ return {
230
+ ...options.existing,
231
+ kind: options.kind,
232
+ domainName: options.sourceDomainName,
233
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
234
+ senderLocalIndex: options.sender.localIndex,
235
+ recipientDomainName: options.targetDomainName,
236
+ amountCogtoshi: options.amountCogtoshi,
237
+ reviewPayloadHex: options.reviewPayloadHex,
238
+ status: "draft",
239
+ lastUpdatedAtUnixMs: options.nowUnixMs,
240
+ attemptedTxid: null,
241
+ attemptedWtxid: null,
242
+ temporaryBuilderLockedOutpoints: [],
243
+ };
244
+ }
245
+ return {
246
+ mutationId: randomBytes(12).toString("hex"),
247
+ kind: options.kind,
248
+ domainName: options.sourceDomainName,
249
+ parentDomainName: null,
250
+ senderScriptPubKeyHex: options.sender.scriptPubKeyHex,
251
+ senderLocalIndex: options.sender.localIndex,
252
+ amountCogtoshi: options.amountCogtoshi,
253
+ recipientDomainName: options.targetDomainName,
254
+ reviewPayloadHex: options.reviewPayloadHex,
255
+ intentFingerprintHex: options.intentFingerprintHex,
256
+ status: "draft",
257
+ createdAtUnixMs: options.nowUnixMs,
258
+ lastUpdatedAtUnixMs: options.nowUnixMs,
259
+ attemptedTxid: null,
260
+ attemptedWtxid: null,
261
+ temporaryBuilderLockedOutpoints: [],
262
+ };
263
+ }
264
+ async function saveUpdatedMutationState(options) {
265
+ const nextState = {
266
+ ...options.state,
267
+ stateRevision: options.state.stateRevision + 1,
268
+ lastWrittenAtUnixMs: options.nowUnixMs,
269
+ };
270
+ await saveWalletStatePreservingUnlock({
271
+ state: nextState,
272
+ provider: options.provider,
273
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
274
+ nowUnixMs: options.nowUnixMs,
275
+ paths: options.paths,
276
+ });
277
+ return nextState;
278
+ }
279
+ function mutationNeedsRepair(mutation, context) {
280
+ if (context.snapshot === null || mutation.recipientDomainName == null) {
281
+ return false;
282
+ }
283
+ const sourceDomain = lookupDomain(context.snapshot.state, mutation.domainName);
284
+ const targetDomain = lookupDomain(context.snapshot.state, mutation.recipientDomainName);
285
+ if (sourceDomain === null || targetDomain === null) {
286
+ return true;
287
+ }
288
+ return !sourceDomain.anchored
289
+ || !targetDomain.anchored
290
+ || Buffer.from(sourceDomain.ownerScriptPubKey).toString("hex") !== mutation.senderScriptPubKeyHex;
291
+ }
292
+ async function reconcilePendingReputationMutation(options) {
293
+ if (options.mutation.status === "confirmed" || options.mutation.status === "live") {
294
+ return {
295
+ state: options.state,
296
+ mutation: options.mutation,
297
+ resolution: options.mutation.status,
298
+ };
299
+ }
300
+ if (options.mutation.status === "repair-required") {
301
+ return {
302
+ state: options.state,
303
+ mutation: options.mutation,
304
+ resolution: "repair-required",
305
+ };
306
+ }
307
+ const walletTx = options.mutation.attemptedTxid === null
308
+ ? null
309
+ : await options.rpc.getTransaction(options.walletName, options.mutation.attemptedTxid).catch(() => null);
310
+ if (walletTx !== null) {
311
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
312
+ const status = walletTx.confirmations > 0 ? "confirmed" : "live";
313
+ const nextMutation = updateMutationRecord(options.mutation, status, options.nowUnixMs, {
314
+ temporaryBuilderLockedOutpoints: [],
315
+ });
316
+ let nextState = upsertPendingMutation(options.state, nextMutation);
317
+ nextState = await saveUpdatedMutationState({
318
+ state: nextState,
319
+ provider: options.provider,
320
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
321
+ nowUnixMs: options.nowUnixMs,
322
+ paths: options.paths,
323
+ });
324
+ return {
325
+ state: nextState,
326
+ mutation: nextMutation,
327
+ resolution: status,
328
+ };
329
+ }
330
+ if (mutationNeedsRepair(options.mutation, options.context)) {
331
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
332
+ const repair = updateMutationRecord(options.mutation, "repair-required", options.nowUnixMs, {
333
+ temporaryBuilderLockedOutpoints: [],
334
+ });
335
+ let nextState = upsertPendingMutation(options.state, repair);
336
+ nextState = await saveUpdatedMutationState({
337
+ state: nextState,
338
+ provider: options.provider,
339
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
340
+ nowUnixMs: options.nowUnixMs,
341
+ paths: options.paths,
342
+ });
343
+ return { state: nextState, mutation: repair, resolution: "repair-required" };
344
+ }
345
+ if (options.mutation.status === "broadcast-unknown"
346
+ || options.mutation.status === "draft"
347
+ || options.mutation.status === "broadcasting") {
348
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
349
+ const canceled = updateMutationRecord(options.mutation, "canceled", options.nowUnixMs, {
350
+ temporaryBuilderLockedOutpoints: [],
351
+ });
352
+ let nextState = upsertPendingMutation(options.state, canceled);
353
+ nextState = await saveUpdatedMutationState({
354
+ state: nextState,
355
+ provider: options.provider,
356
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
357
+ nowUnixMs: options.nowUnixMs,
358
+ paths: options.paths,
359
+ });
360
+ return { state: nextState, mutation: canceled, resolution: "not-seen" };
361
+ }
362
+ return {
363
+ state: options.state,
364
+ mutation: options.mutation,
365
+ resolution: "continue",
366
+ };
367
+ }
368
+ async function confirmYesNo(prompter, message, errorCode, options) {
369
+ await confirmSharedYesNo(prompter, message, {
370
+ assumeYes: options.assumeYes,
371
+ errorCode,
372
+ requiresTtyErrorCode: options.requiresTtyErrorCode,
373
+ });
374
+ }
375
+ async function confirmTyped(prompter, expected, prompt, errorCode, options) {
376
+ await confirmSharedTypedAcknowledgement(prompter, {
377
+ assumeYes: options.assumeYes,
378
+ expected,
379
+ prompt,
380
+ errorCode,
381
+ requiresTtyErrorCode: options.requiresTtyErrorCode,
382
+ typedAckRequiredErrorCode: options.typedAckRequiredErrorCode,
383
+ });
384
+ }
385
+ async function confirmReputationMutation(prompter, options) {
386
+ prompter.writeLine(`${options.kind === "give" ? "Giving" : "Revoking"} reputation from "${options.sourceDomainName}" to "${options.targetDomainName}".`);
387
+ prompter.writeLine(`Resolved sender: ${options.resolved.sender.selector} (${options.resolved.sender.address})`);
388
+ prompter.writeLine(`Burn amount: ${formatCogAmount(options.amountCogtoshi)}`);
389
+ prompter.writeLine(`Effect: ${describeReputationEffect(options.resolved.effect)}.`);
390
+ prompter.writeLine(`Review: ${describeReputationReview(options.resolved.review)}.`);
391
+ if (options.reviewText !== null) {
392
+ prompter.writeLine("Warning: review text will be encoded and published publicly in the mempool and on-chain.");
393
+ }
394
+ if (options.kind === "give" && options.resolved.selfStake) {
395
+ prompter.writeLine("Self-stake: yes.");
396
+ prompter.writeLine("Warning: this is self-stake.");
397
+ prompter.writeLine("Self-stake is irrevocable and cannot later be revoked.");
398
+ await confirmTyped(prompter, options.sourceDomainName, `Type ${options.sourceDomainName} to continue: `, "wallet_rep_give_confirmation_rejected", {
399
+ assumeYes: options.assumeYes,
400
+ requiresTtyErrorCode: "wallet_rep_give_requires_tty",
401
+ typedAckRequiredErrorCode: "wallet_rep_give_typed_ack_required",
402
+ });
403
+ return;
404
+ }
405
+ await confirmYesNo(prompter, options.kind === "give"
406
+ ? "This burns COG to publish a reputation commitment."
407
+ : "This revokes visible support but the burned COG is not refunded.", options.kind === "give"
408
+ ? "wallet_rep_give_confirmation_rejected"
409
+ : "wallet_rep_revoke_confirmation_rejected", {
410
+ assumeYes: options.assumeYes,
411
+ requiresTtyErrorCode: options.kind === "give"
412
+ ? "wallet_rep_give_requires_tty"
413
+ : "wallet_rep_revoke_requires_tty",
414
+ });
415
+ }
416
+ async function encodeReviewText(reviewText, errorPrefix) {
417
+ const trimmed = reviewText?.trim() ?? "";
418
+ if (trimmed === "") {
419
+ return {
420
+ text: null,
421
+ payload: undefined,
422
+ payloadHex: null,
423
+ };
424
+ }
425
+ return encodeSentence(trimmed)
426
+ .then((payload) => ({
427
+ text: trimmed,
428
+ payload,
429
+ payloadHex: Buffer.from(payload).toString("hex"),
430
+ }))
431
+ .catch((error) => {
432
+ throw new Error(error instanceof Error ? `${errorPrefix}_invalid_review_${error.message}` : `${errorPrefix}_invalid_review`);
433
+ });
434
+ }
435
+ async function sendBuiltTransaction(options) {
436
+ let nextState = options.state;
437
+ const broadcasting = updateMutationRecord(options.mutation, "broadcasting", options.nowUnixMs, {
438
+ attemptedTxid: options.built.txid,
439
+ attemptedWtxid: options.built.wtxid,
440
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
441
+ });
442
+ nextState = upsertPendingMutation(nextState, broadcasting);
443
+ nextState = await saveUpdatedMutationState({
444
+ state: nextState,
445
+ provider: options.provider,
446
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
447
+ nowUnixMs: options.nowUnixMs,
448
+ paths: options.paths,
449
+ });
450
+ if (options.snapshotHeight !== null && options.snapshotHeight !== (await options.rpc.getBlockchainInfo()).blocks) {
451
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
452
+ throw new Error(`${options.errorPrefix}_tip_mismatch`);
453
+ }
454
+ try {
455
+ await options.rpc.sendRawTransaction(options.built.rawHex);
456
+ }
457
+ catch (error) {
458
+ if (!isAlreadyAcceptedError(error)) {
459
+ if (isBroadcastUnknownError(error)) {
460
+ const unknown = updateMutationRecord(broadcasting, "broadcast-unknown", options.nowUnixMs, {
461
+ attemptedTxid: options.built.txid,
462
+ attemptedWtxid: options.built.wtxid,
463
+ temporaryBuilderLockedOutpoints: options.built.temporaryBuilderLockedOutpoints,
464
+ });
465
+ nextState = upsertPendingMutation(nextState, unknown);
466
+ nextState = await saveUpdatedMutationState({
467
+ state: nextState,
468
+ provider: options.provider,
469
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
470
+ nowUnixMs: options.nowUnixMs,
471
+ paths: options.paths,
472
+ });
473
+ throw new Error(`${options.errorPrefix}_broadcast_unknown`);
474
+ }
475
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
476
+ const canceled = updateMutationRecord(broadcasting, "canceled", options.nowUnixMs, {
477
+ attemptedTxid: options.built.txid,
478
+ attemptedWtxid: options.built.wtxid,
479
+ temporaryBuilderLockedOutpoints: [],
480
+ });
481
+ nextState = upsertPendingMutation(nextState, canceled);
482
+ nextState = await saveUpdatedMutationState({
483
+ state: nextState,
484
+ provider: options.provider,
485
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
486
+ nowUnixMs: options.nowUnixMs,
487
+ paths: options.paths,
488
+ });
489
+ throw error;
490
+ }
491
+ }
492
+ await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.built.temporaryBuilderLockedOutpoints);
493
+ const live = updateMutationRecord(broadcasting, "live", options.nowUnixMs, {
494
+ attemptedTxid: options.built.txid,
495
+ attemptedWtxid: options.built.wtxid,
496
+ temporaryBuilderLockedOutpoints: [],
497
+ });
498
+ nextState = upsertPendingMutation(nextState, live);
499
+ nextState = await saveUpdatedMutationState({
500
+ state: nextState,
501
+ provider: options.provider,
502
+ unlockUntilUnixMs: options.unlockUntilUnixMs,
503
+ nowUnixMs: options.nowUnixMs,
504
+ paths: options.paths,
505
+ });
506
+ return { state: nextState, mutation: live };
507
+ }
508
+ async function submitReputationMutation(options) {
509
+ if (!options.prompter.isInteractive && options.assumeYes !== true) {
510
+ throw new Error(`${options.errorPrefix}_requires_tty`);
511
+ }
512
+ if (options.amountCogtoshi <= 0n) {
513
+ throw new Error(`${options.errorPrefix}_invalid_amount`);
514
+ }
515
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
516
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
517
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
518
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
519
+ purpose: options.errorPrefix,
520
+ walletRootId: null,
521
+ });
522
+ try {
523
+ const miningPreemption = await pauseMiningForWalletMutation({
524
+ paths,
525
+ reason: options.errorPrefix,
526
+ });
527
+ const readContext = await (options.openReadContext ?? openWalletReadContext)({
528
+ dataDir: options.dataDir,
529
+ databasePath: options.databasePath,
530
+ secretProvider: provider,
531
+ paths,
532
+ });
533
+ try {
534
+ const normalizedSourceDomainName = normalizeDomainName(options.sourceDomainName, `${options.errorPrefix}_missing_source_domain`);
535
+ const normalizedTargetDomainName = normalizeDomainName(options.targetDomainName, `${options.errorPrefix}_missing_target_domain`);
536
+ const operation = resolveReputationOperation(readContext, normalizedSourceDomainName, normalizedTargetDomainName, options.errorPrefix);
537
+ if (operation.availableBalanceCogtoshi < options.amountCogtoshi) {
538
+ throw new Error(`${options.errorPrefix}_insufficient_cog_balance`);
539
+ }
540
+ if (options.kind === "rep-revoke") {
541
+ if (operation.sourceDomain.domainId === operation.targetDomain.domainId) {
542
+ throw new Error(`${options.errorPrefix}_self_revoke_not_allowed`);
543
+ }
544
+ if (options.amountCogtoshi > operation.currentNetSupportCogtoshi) {
545
+ throw new Error(`${options.errorPrefix}_amount_exceeds_net_support`);
546
+ }
547
+ }
548
+ const review = await encodeReviewText(options.reviewText, options.errorPrefix);
549
+ const selfStake = operation.sourceDomain.domainId === operation.targetDomain.domainId;
550
+ const resolved = createResolvedReputationSummary({
551
+ kind: options.kind === "rep-give" ? "give" : "revoke",
552
+ sender: operation.sender,
553
+ senderSelector: operation.senderSelector,
554
+ amountCogtoshi: options.amountCogtoshi,
555
+ review,
556
+ selfStake,
557
+ });
558
+ const intentFingerprintHex = createIntentFingerprint([
559
+ options.kind,
560
+ operation.state.walletRootId,
561
+ operation.sourceDomain.name,
562
+ operation.targetDomain.name,
563
+ options.amountCogtoshi,
564
+ review.payloadHex ?? "",
565
+ ]);
566
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
567
+ dataDir: options.dataDir,
568
+ chain: "main",
569
+ startHeight: 0,
570
+ walletRootId: operation.state.walletRootId,
571
+ });
572
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
573
+ const walletName = operation.state.managedCoreWallet.walletName;
574
+ const existingMutation = findPendingMutationByIntent(operation.state, intentFingerprintHex);
575
+ if (existingMutation !== null) {
576
+ const reconciled = await reconcilePendingReputationMutation({
577
+ state: operation.state,
578
+ mutation: existingMutation,
579
+ provider,
580
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
581
+ nowUnixMs,
582
+ paths,
583
+ rpc,
584
+ walletName,
585
+ context: readContext,
586
+ });
587
+ if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
588
+ return {
589
+ kind: options.kind === "rep-give" ? "give" : "revoke",
590
+ sourceDomainName: normalizedSourceDomainName,
591
+ targetDomainName: normalizedTargetDomainName,
592
+ amountCogtoshi: options.amountCogtoshi,
593
+ txid: reconciled.mutation.attemptedTxid ?? "unknown",
594
+ status: reconciled.resolution,
595
+ reusedExisting: true,
596
+ reviewIncluded: review.payloadHex !== null,
597
+ resolved,
598
+ };
599
+ }
600
+ if (reconciled.resolution === "repair-required") {
601
+ throw new Error(`${options.errorPrefix}_repair_required`);
602
+ }
603
+ }
604
+ await confirmReputationMutation(options.prompter, {
605
+ kind: options.kind === "rep-give" ? "give" : "revoke",
606
+ sourceDomainName: normalizedSourceDomainName,
607
+ targetDomainName: normalizedTargetDomainName,
608
+ amountCogtoshi: options.amountCogtoshi,
609
+ reviewText: review.text,
610
+ resolved,
611
+ assumeYes: options.assumeYes,
612
+ });
613
+ let nextState = upsertPendingMutation(operation.state, createDraftMutation({
614
+ kind: options.kind,
615
+ sourceDomainName: normalizedSourceDomainName,
616
+ targetDomainName: normalizedTargetDomainName,
617
+ amountCogtoshi: options.amountCogtoshi,
618
+ sender: operation.sender,
619
+ intentFingerprintHex,
620
+ nowUnixMs,
621
+ reviewPayloadHex: review.payloadHex,
622
+ existing: existingMutation,
623
+ }));
624
+ nextState = await saveUpdatedMutationState({
625
+ state: nextState,
626
+ provider,
627
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
628
+ nowUnixMs,
629
+ paths,
630
+ });
631
+ const opReturnData = options.kind === "rep-give"
632
+ ? serializeRepCommit(operation.sourceDomain.domainId, operation.targetDomain.domainId, options.amountCogtoshi, review.payload).opReturnData
633
+ : serializeRepRevoke(operation.sourceDomain.domainId, operation.targetDomain.domainId, options.amountCogtoshi, review.payload).opReturnData;
634
+ const built = await buildTransaction({
635
+ rpc,
636
+ walletName,
637
+ plan: buildPlanForReputationOperation({
638
+ state: nextState,
639
+ allUtxos: await rpc.listUnspent(walletName, 1),
640
+ sender: operation.sender,
641
+ anchorOutpoint: operation.anchorOutpoint,
642
+ opReturnData,
643
+ errorPrefix: options.errorPrefix,
644
+ }),
645
+ });
646
+ const final = await sendBuiltTransaction({
647
+ rpc,
648
+ walletName,
649
+ snapshotHeight: readContext.snapshot?.tip?.height ?? null,
650
+ built,
651
+ mutation: nextState.pendingMutations.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex),
652
+ state: nextState,
653
+ provider,
654
+ unlockUntilUnixMs: operation.unlockUntilUnixMs,
655
+ nowUnixMs,
656
+ paths,
657
+ errorPrefix: options.errorPrefix,
658
+ });
659
+ return {
660
+ kind: options.kind === "rep-give" ? "give" : "revoke",
661
+ sourceDomainName: normalizedSourceDomainName,
662
+ targetDomainName: normalizedTargetDomainName,
663
+ amountCogtoshi: options.amountCogtoshi,
664
+ txid: final.mutation.attemptedTxid ?? built.txid,
665
+ status: "live",
666
+ reusedExisting: false,
667
+ reviewIncluded: review.payloadHex !== null,
668
+ resolved,
669
+ };
670
+ }
671
+ finally {
672
+ await readContext.close();
673
+ await miningPreemption.release();
674
+ }
675
+ }
676
+ finally {
677
+ await controlLock.release();
678
+ }
679
+ }
680
+ export async function giveReputation(options) {
681
+ return submitReputationMutation({
682
+ ...options,
683
+ kind: "rep-give",
684
+ errorPrefix: "wallet_rep_give",
685
+ });
686
+ }
687
+ export async function revokeReputation(options) {
688
+ return submitReputationMutation({
689
+ ...options,
690
+ kind: "rep-revoke",
691
+ errorPrefix: "wallet_rep_revoke",
692
+ });
693
+ }