@cogcoin/client 0.5.15 → 1.0.1
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.
- package/README.md +80 -25
- package/dist/app-paths.d.ts +5 -6
- package/dist/app-paths.js +8 -16
- package/dist/art/balance.txt +10 -0
- package/dist/art/welcome.txt +16 -0
- package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
- package/dist/bitcoind/bootstrap/controller.js +53 -1
- package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
- package/dist/bitcoind/client/follow-block-times.js +1 -1
- package/dist/bitcoind/client/internal-types.d.ts +7 -3
- package/dist/bitcoind/client/managed-client.d.ts +4 -2
- package/dist/bitcoind/client/managed-client.js +14 -0
- package/dist/bitcoind/client/sync-engine.js +72 -11
- package/dist/bitcoind/hash-order.d.ts +4 -0
- package/dist/bitcoind/hash-order.js +13 -0
- package/dist/bitcoind/indexer-daemon-main.js +11 -3
- package/dist/bitcoind/normalize.js +3 -2
- package/dist/bitcoind/processing-start-height.d.ts +5 -0
- package/dist/bitcoind/processing-start-height.js +7 -0
- package/dist/bitcoind/progress/constants.d.ts +4 -0
- package/dist/bitcoind/progress/constants.js +4 -0
- package/dist/bitcoind/progress/controller.d.ts +2 -1
- package/dist/bitcoind/progress/controller.js +3 -3
- package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
- package/dist/bitcoind/progress/follow-scene.js +29 -6
- package/dist/bitcoind/progress/formatting.d.ts +1 -0
- package/dist/bitcoind/progress/formatting.js +6 -0
- package/dist/bitcoind/progress/train-scene.js +37 -18
- package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
- package/dist/bitcoind/progress/tty-renderer.js +8 -4
- package/dist/bitcoind/rpc.d.ts +2 -1
- package/dist/bitcoind/rpc.js +3 -0
- package/dist/bitcoind/types.d.ts +6 -0
- package/dist/bytes.d.ts +1 -0
- package/dist/bytes.js +3 -0
- package/dist/cli/art.d.ts +2 -0
- package/dist/cli/art.js +37 -0
- package/dist/cli/commands/client-admin.d.ts +2 -0
- package/dist/cli/commands/client-admin.js +91 -0
- package/dist/cli/commands/follow.js +0 -2
- package/dist/cli/commands/mining-admin.js +6 -47
- package/dist/cli/commands/mining-read.js +11 -50
- package/dist/cli/commands/mining-runtime.js +142 -5
- package/dist/cli/commands/service-runtime.js +0 -2
- package/dist/cli/commands/status.js +8 -2
- package/dist/cli/commands/sync.js +49 -92
- package/dist/cli/commands/wallet-admin.js +142 -136
- package/dist/cli/commands/wallet-mutation.js +91 -79
- package/dist/cli/commands/wallet-read.js +15 -18
- package/dist/cli/context.js +5 -14
- package/dist/cli/mining-format.d.ts +0 -1
- package/dist/cli/mining-format.js +5 -37
- package/dist/cli/mining-json.d.ts +0 -18
- package/dist/cli/mining-json.js +0 -35
- package/dist/cli/mutation-command-groups.d.ts +1 -2
- package/dist/cli/mutation-command-groups.js +0 -5
- package/dist/cli/mutation-json.d.ts +24 -145
- package/dist/cli/mutation-json.js +30 -136
- package/dist/cli/mutation-resolved-json.d.ts +0 -7
- package/dist/cli/mutation-resolved-json.js +4 -10
- package/dist/cli/mutation-success.d.ts +2 -0
- package/dist/cli/mutation-success.js +11 -1
- package/dist/cli/mutation-text-format.js +1 -3
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +254 -231
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +93 -122
- package/dist/cli/preview-json.d.ts +17 -120
- package/dist/cli/preview-json.js +14 -97
- package/dist/cli/prompt.js +8 -13
- package/dist/cli/read-json.d.ts +15 -37
- package/dist/cli/read-json.js +44 -140
- package/dist/cli/runner.js +10 -13
- package/dist/cli/sync-progress.d.ts +6 -0
- package/dist/cli/sync-progress.js +91 -0
- package/dist/cli/types.d.ts +9 -17
- package/dist/cli/types.js +0 -2
- package/dist/cli/wallet-format.d.ts +1 -0
- package/dist/cli/wallet-format.js +208 -144
- package/dist/cli/workflow-hints.d.ts +3 -3
- package/dist/cli/workflow-hints.js +11 -8
- package/dist/client/default-client.d.ts +3 -1
- package/dist/client/default-client.js +45 -2
- package/dist/client/factory.js +1 -1
- package/dist/client/initialization.js +23 -0
- package/dist/client/persistence.js +5 -5
- package/dist/client/store-adapter.js +1 -0
- package/dist/sqlite/checkpoints.d.ts +1 -0
- package/dist/sqlite/checkpoints.js +7 -0
- package/dist/sqlite/store.js +14 -1
- package/dist/types.d.ts +1 -0
- package/dist/wallet/coin-control.d.ts +41 -12
- package/dist/wallet/coin-control.js +100 -428
- package/dist/wallet/descriptor-normalization.d.ts +1 -3
- package/dist/wallet/descriptor-normalization.js +0 -16
- package/dist/wallet/lifecycle.d.ts +7 -99
- package/dist/wallet/lifecycle.js +513 -968
- package/dist/wallet/managed-core-wallet.d.ts +13 -0
- package/dist/wallet/managed-core-wallet.js +20 -0
- package/dist/wallet/mining/constants.d.ts +5 -12
- package/dist/wallet/mining/constants.js +5 -12
- package/dist/wallet/mining/control.d.ts +1 -13
- package/dist/wallet/mining/control.js +45 -349
- package/dist/wallet/mining/index.d.ts +4 -5
- package/dist/wallet/mining/index.js +2 -3
- package/dist/wallet/mining/runner.d.ts +123 -13
- package/dist/wallet/mining/runner.js +899 -511
- package/dist/wallet/mining/runtime-artifacts.js +23 -3
- package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
- package/dist/wallet/mining/sentence-protocol.js +123 -0
- package/dist/wallet/mining/sentences.d.ts +4 -8
- package/dist/wallet/mining/sentences.js +3 -52
- package/dist/wallet/mining/state.d.ts +11 -6
- package/dist/wallet/mining/state.js +7 -6
- package/dist/wallet/mining/types.d.ts +2 -30
- package/dist/wallet/mining/visualizer.d.ts +31 -3
- package/dist/wallet/mining/visualizer.js +135 -13
- package/dist/wallet/read/context.d.ts +0 -2
- package/dist/wallet/read/context.js +119 -140
- package/dist/wallet/read/filter.js +2 -11
- package/dist/wallet/read/index.d.ts +1 -1
- package/dist/wallet/read/project.js +24 -77
- package/dist/wallet/read/types.d.ts +10 -25
- package/dist/wallet/reset.d.ts +0 -1
- package/dist/wallet/reset.js +60 -138
- package/dist/wallet/root-resolution.d.ts +1 -5
- package/dist/wallet/root-resolution.js +0 -18
- package/dist/wallet/runtime.d.ts +0 -6
- package/dist/wallet/runtime.js +0 -8
- package/dist/wallet/state/client-password-agent.js +208 -0
- package/dist/wallet/state/client-password.d.ts +65 -0
- package/dist/wallet/state/client-password.js +952 -0
- package/dist/wallet/state/crypto.d.ts +1 -20
- package/dist/wallet/state/crypto.js +0 -63
- package/dist/wallet/state/provider.d.ts +23 -11
- package/dist/wallet/state/provider.js +248 -290
- package/dist/wallet/state/storage.d.ts +2 -2
- package/dist/wallet/state/storage.js +48 -16
- package/dist/wallet/tx/anchor.d.ts +3 -28
- package/dist/wallet/tx/anchor.js +349 -1250
- package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
- package/dist/wallet/tx/bitcoin-transfer.js +200 -0
- package/dist/wallet/tx/cog.d.ts +5 -1
- package/dist/wallet/tx/cog.js +149 -185
- package/dist/wallet/tx/common.d.ts +61 -8
- package/dist/wallet/tx/common.js +266 -146
- package/dist/wallet/tx/domain-admin.d.ts +3 -1
- package/dist/wallet/tx/domain-admin.js +61 -99
- package/dist/wallet/tx/domain-market.d.ts +5 -1
- package/dist/wallet/tx/domain-market.js +221 -228
- package/dist/wallet/tx/field.d.ts +4 -10
- package/dist/wallet/tx/field.js +83 -924
- package/dist/wallet/tx/identity-selector.d.ts +9 -3
- package/dist/wallet/tx/identity-selector.js +17 -35
- package/dist/wallet/tx/index.d.ts +3 -1
- package/dist/wallet/tx/index.js +2 -1
- package/dist/wallet/tx/register.d.ts +3 -1
- package/dist/wallet/tx/register.js +62 -220
- package/dist/wallet/tx/reputation.d.ts +3 -1
- package/dist/wallet/tx/reputation.js +58 -95
- package/dist/wallet/types.d.ts +8 -122
- package/package.json +5 -5
- package/dist/wallet/archive.d.ts +0 -4
- package/dist/wallet/archive.js +0 -41
- package/dist/wallet/mining/hook-protocol.d.ts +0 -47
- package/dist/wallet/mining/hook-protocol.js +0 -161
- package/dist/wallet/mining/hook-runner.js +0 -52
- package/dist/wallet/mining/hooks.d.ts +0 -38
- package/dist/wallet/mining/hooks.js +0 -520
- package/dist/wallet/state/explicit-lock.d.ts +0 -4
- package/dist/wallet/state/explicit-lock.js +0 -19
- package/dist/wallet/state/session.d.ts +0 -12
- package/dist/wallet/state/session.js +0 -23
- /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
package/dist/wallet/tx/anchor.js
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { encodeSentence } from "@cogcoin/scoring";
|
|
3
|
-
import {
|
|
3
|
+
import { lookupDomain } from "@cogcoin/indexer/queries";
|
|
4
4
|
import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
|
|
5
5
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
6
6
|
import { acquireFileLock } from "../fs/lock.js";
|
|
7
|
-
import { deriveWalletIdentityMaterial, } from "../material.js";
|
|
8
7
|
import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
|
|
9
8
|
import { createDefaultWalletSecretProvider, } from "../state/provider.js";
|
|
10
|
-
import { serializeDomainAnchor,
|
|
9
|
+
import { serializeDomainAnchor, validateDomainName, } from "../cogop/index.js";
|
|
11
10
|
import { openWalletReadContext } from "../read/index.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
const ACTIVE_FAMILY_STATUSES = new Set([
|
|
15
|
-
"draft",
|
|
16
|
-
"broadcasting",
|
|
17
|
-
"broadcast-unknown",
|
|
18
|
-
"live",
|
|
19
|
-
"repair-required",
|
|
20
|
-
]);
|
|
11
|
+
import { assertWalletMutationContextReady, buildWalletMutationTransactionWithReserveFallback, createBuiltWalletMutationFeeSummary, createWalletMutationFeeMetadata, isAlreadyAcceptedError, isBroadcastUnknownError, mergeFixedWalletInputs, outpointKey, pauseMiningForWalletMutation, resolvePendingMutationReuseDecision, resolveWalletMutationFeeSelection, saveWalletStatePreservingUnlock, unlockTemporaryBuilderLocks, updateMutationRecord, } from "./common.js";
|
|
12
|
+
import { findPendingMutationByIntent, upsertPendingMutation } from "./journal.js";
|
|
21
13
|
function normalizeDomainName(domainName) {
|
|
22
14
|
const normalized = domainName.trim().toLowerCase();
|
|
23
15
|
if (normalized.length === 0) {
|
|
@@ -57,11 +49,6 @@ function createIntentFingerprint(parts) {
|
|
|
57
49
|
.update(parts.map((part) => String(part)).join("\n"))
|
|
58
50
|
.digest("hex");
|
|
59
51
|
}
|
|
60
|
-
function isSpendableConfirmedUtxo(entry) {
|
|
61
|
-
return entry.confirmations >= 1
|
|
62
|
-
&& entry.spendable !== false
|
|
63
|
-
&& entry.safe !== false;
|
|
64
|
-
}
|
|
65
52
|
function sortUtxos(entries) {
|
|
66
53
|
return entries
|
|
67
54
|
.slice()
|
|
@@ -69,139 +56,11 @@ function sortUtxos(entries) {
|
|
|
69
56
|
|| left.txid.localeCompare(right.txid)
|
|
70
57
|
|| left.vout - right.vout);
|
|
71
58
|
}
|
|
72
|
-
function
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
status: "dedicated",
|
|
78
|
-
assignedDomainNames: [],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function withUpdatedAssignedDomain(options) {
|
|
82
|
-
return options.identities.map((identity) => {
|
|
83
|
-
let assigned = identity.assignedDomainNames.filter((name) => name !== options.domainName);
|
|
84
|
-
if (identity.index === options.targetLocalIndex) {
|
|
85
|
-
assigned = [...assigned, options.domainName];
|
|
86
|
-
}
|
|
87
|
-
if (identity.index !== options.sourceLocalIndex && identity.index !== options.targetLocalIndex) {
|
|
88
|
-
assigned = identity.assignedDomainNames.slice();
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
...identity,
|
|
92
|
-
assignedDomainNames: assigned.sort((left, right) => left.localeCompare(right)),
|
|
93
|
-
};
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
function upsertProactiveFamily(state, family) {
|
|
97
|
-
const families = state.proactiveFamilies.slice();
|
|
98
|
-
const existingIndex = families.findIndex((entry) => entry.familyId === family.familyId);
|
|
99
|
-
if (existingIndex >= 0) {
|
|
100
|
-
families[existingIndex] = family;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
families.push(family);
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
...state,
|
|
107
|
-
proactiveFamilies: families,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function findAnchorFamilyByIntent(state, intentFingerprintHex) {
|
|
111
|
-
return state.proactiveFamilies.find((family) => family.type === "anchor" && family.intentFingerprintHex === intentFingerprintHex) ?? null;
|
|
112
|
-
}
|
|
113
|
-
function findActiveAnchorFamilyByDomain(state, domainName) {
|
|
114
|
-
return state.proactiveFamilies.find((family) => family.type === "anchor"
|
|
115
|
-
&& family.domainName === domainName
|
|
116
|
-
&& ACTIVE_FAMILY_STATUSES.has(family.status)) ?? null;
|
|
117
|
-
}
|
|
118
|
-
function isClearableReservedAnchorFamily(family) {
|
|
119
|
-
return family?.type === "anchor"
|
|
120
|
-
&& family.status === "draft"
|
|
121
|
-
&& family.currentStep === "reserved";
|
|
122
|
-
}
|
|
123
|
-
function findAnchorFamilyById(state, familyId) {
|
|
124
|
-
return state.proactiveFamilies.find((family) => family.familyId === familyId) ?? null;
|
|
125
|
-
}
|
|
126
|
-
function collectActivelyReservedDedicatedIndices(state) {
|
|
127
|
-
const reservedIndices = new Set();
|
|
128
|
-
for (const domain of state.domains) {
|
|
129
|
-
if (domain.dedicatedIndex !== null && domain.localAnchorIntent !== "none") {
|
|
130
|
-
reservedIndices.add(domain.dedicatedIndex);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
for (const family of state.proactiveFamilies) {
|
|
134
|
-
if (family.type === "anchor"
|
|
135
|
-
&& ACTIVE_FAMILY_STATUSES.has(family.status)
|
|
136
|
-
&& family.reservedDedicatedIndex !== null
|
|
137
|
-
&& family.reservedDedicatedIndex !== undefined) {
|
|
138
|
-
reservedIndices.add(family.reservedDedicatedIndex);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return reservedIndices;
|
|
142
|
-
}
|
|
143
|
-
function selectReusableDedicatedIdentityTarget(state) {
|
|
144
|
-
const reservedIndices = collectActivelyReservedDedicatedIndices(state);
|
|
145
|
-
const reusableIdentity = state.identities
|
|
146
|
-
.filter((identity) => identity.status === "dedicated"
|
|
147
|
-
&& identity.address !== null
|
|
148
|
-
&& identity.assignedDomainNames.length === 0
|
|
149
|
-
&& !reservedIndices.has(identity.index))
|
|
150
|
-
.sort((left, right) => left.index - right.index)[0];
|
|
151
|
-
if (reusableIdentity == null) {
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
const material = deriveWalletIdentityMaterial(state.keys.accountXprv, reusableIdentity.index);
|
|
155
|
-
const reusableAddress = reusableIdentity.address;
|
|
156
|
-
if (reusableAddress === null) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
...material,
|
|
161
|
-
localIndex: reusableIdentity.index,
|
|
162
|
-
address: reusableAddress,
|
|
163
|
-
scriptPubKeyHex: reusableIdentity.scriptPubKeyHex,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function selectFreshDedicatedIdentityTarget(state) {
|
|
167
|
-
const unavailableIndices = new Set();
|
|
168
|
-
for (const identity of state.identities) {
|
|
169
|
-
unavailableIndices.add(identity.index);
|
|
170
|
-
}
|
|
171
|
-
for (const domain of state.domains) {
|
|
172
|
-
if (domain.dedicatedIndex !== null) {
|
|
173
|
-
unavailableIndices.add(domain.dedicatedIndex);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
for (const index of collectActivelyReservedDedicatedIndices(state)) {
|
|
177
|
-
unavailableIndices.add(index);
|
|
178
|
-
}
|
|
179
|
-
const startIndex = Math.max(1, state.nextDedicatedIndex);
|
|
180
|
-
for (let index = startIndex; index <= state.descriptor.rangeEnd; index += 1) {
|
|
181
|
-
if (unavailableIndices.has(index)) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const material = deriveWalletIdentityMaterial(state.keys.accountXprv, index);
|
|
185
|
-
return {
|
|
186
|
-
...material,
|
|
187
|
-
localIndex: index,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
throw new Error("wallet_anchor_no_fresh_dedicated_index");
|
|
191
|
-
}
|
|
192
|
-
function selectNextDedicatedIdentityTarget(state) {
|
|
193
|
-
return selectReusableDedicatedIdentityTarget(state) ?? selectFreshDedicatedIdentityTarget(state);
|
|
194
|
-
}
|
|
195
|
-
function deriveAnchorTargetIdentityForIndex(state, localIndex) {
|
|
196
|
-
const existingIdentity = state.identities.find((identity) => identity.index === localIndex
|
|
197
|
-
&& identity.address !== null) ?? null;
|
|
198
|
-
const material = deriveWalletIdentityMaterial(state.keys.accountXprv, localIndex);
|
|
199
|
-
return {
|
|
200
|
-
...material,
|
|
201
|
-
localIndex,
|
|
202
|
-
address: existingIdentity?.address ?? material.address,
|
|
203
|
-
scriptPubKeyHex: existingIdentity?.scriptPubKeyHex ?? material.scriptPubKeyHex,
|
|
204
|
-
};
|
|
59
|
+
function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
|
|
60
|
+
return entry.scriptPubKey === fundingScriptPubKeyHex
|
|
61
|
+
&& entry.confirmations >= 1
|
|
62
|
+
&& entry.spendable !== false
|
|
63
|
+
&& entry.safe !== false;
|
|
205
64
|
}
|
|
206
65
|
function encodeFoundingMessage(foundingMessageText) {
|
|
207
66
|
const trimmed = foundingMessageText?.trim() ?? "";
|
|
@@ -249,902 +108,226 @@ async function resolveFoundingMessage(options) {
|
|
|
249
108
|
}
|
|
250
109
|
}
|
|
251
110
|
}
|
|
252
|
-
function
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
return {
|
|
260
|
-
txid: anchoredDomain.currentCanonicalAnchorOutpoint.txid,
|
|
261
|
-
vout: anchoredDomain.currentCanonicalAnchorOutpoint.vout,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function isFundingSender(state, sender) {
|
|
265
|
-
return sender.scriptPubKeyHex === state.funding.scriptPubKeyHex;
|
|
266
|
-
}
|
|
267
|
-
async function confirmAnchor(prompter, operation) {
|
|
268
|
-
prompter.writeLine(`You are anchoring "${operation.chainDomain.name}" onto dedicated index ${operation.targetIdentity.localIndex}.`);
|
|
269
|
-
prompter.writeLine("Anchoring is permanent chain state. This flow uses two transactions and is not rolled back automatically.");
|
|
270
|
-
prompter.writeLine(`Dedicated BTC address: ${operation.targetIdentity.address}`);
|
|
271
|
-
prompter.writeLine(`Dedicated Ethereum address: ${operation.targetIdentity.ethereumAddress}`);
|
|
272
|
-
prompter.writeLine(`Dedicated Nostr npub: ${operation.targetIdentity.nostrNpub}`);
|
|
273
|
-
if (operation.foundingMessageText !== null) {
|
|
111
|
+
async function confirmDirectAnchor(prompter, options) {
|
|
112
|
+
prompter.writeLine(`You are anchoring "${options.domainName}".`);
|
|
113
|
+
prompter.writeLine(`Wallet address: ${options.walletAddress}`);
|
|
114
|
+
prompter.writeLine("Anchoring publishes a standalone DOMAIN_ANCHOR from the local wallet address.");
|
|
115
|
+
if (options.foundingMessageText !== null) {
|
|
274
116
|
prompter.writeLine("The founding message bytes will be public in mempool and on-chain.");
|
|
275
|
-
prompter.writeLine(`Founding message: ${
|
|
276
|
-
}
|
|
277
|
-
if (operation.hadListing) {
|
|
278
|
-
prompter.writeLine("Warning: Tx1 will cancel the current listing for this domain.");
|
|
279
|
-
prompter.writeLine("That listing-cancel side effect is not rolled back automatically if Tx2 later fails.");
|
|
117
|
+
prompter.writeLine(`Founding message: ${options.foundingMessageText}`);
|
|
280
118
|
}
|
|
281
119
|
const answer = (await prompter.prompt("Type the domain name to continue: ")).trim();
|
|
282
|
-
if (answer !==
|
|
120
|
+
if (answer !== options.domainName) {
|
|
283
121
|
throw new Error("wallet_anchor_confirmation_rejected");
|
|
284
122
|
}
|
|
285
123
|
}
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
124
|
+
function buildDirectAnchorPlan(options) {
|
|
125
|
+
const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => isSpendableFundingUtxo(entry, options.state.funding.scriptPubKeyHex)));
|
|
126
|
+
const foundingPayload = options.foundingMessagePayloadHex === null
|
|
127
|
+
? undefined
|
|
128
|
+
: Buffer.from(options.foundingMessagePayloadHex, "hex");
|
|
129
|
+
const opReturnData = serializeDomainAnchor(options.domainId, foundingPayload).opReturnData;
|
|
130
|
+
return {
|
|
131
|
+
fixedInputs: [],
|
|
132
|
+
outputs: [{ data: Buffer.from(opReturnData).toString("hex") }],
|
|
133
|
+
changeAddress: options.state.funding.address,
|
|
134
|
+
changePosition: 1,
|
|
135
|
+
expectedOpReturnScriptHex: encodeOpReturnScript(opReturnData),
|
|
136
|
+
allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
137
|
+
eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
|
|
138
|
+
};
|
|
296
139
|
}
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
throw new Error("wallet_anchor_domain_not_found");
|
|
302
|
-
}
|
|
303
|
-
if (chainDomain.anchored) {
|
|
304
|
-
throw new Error("wallet_anchor_domain_already_anchored");
|
|
140
|
+
function validateDirectAnchorDraft(decoded, funded, plan) {
|
|
141
|
+
const outputs = decoded.tx.vout;
|
|
142
|
+
if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
|
|
143
|
+
throw new Error("wallet_anchor_opreturn_mismatch");
|
|
305
144
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
145
|
+
if (funded.changepos === -1) {
|
|
146
|
+
if (outputs.length !== 1) {
|
|
147
|
+
throw new Error("wallet_anchor_unexpected_output_count");
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
310
150
|
}
|
|
311
|
-
if (
|
|
312
|
-
throw new Error("
|
|
151
|
+
if (funded.changepos !== plan.changePosition || outputs.length !== 2) {
|
|
152
|
+
throw new Error("wallet_anchor_change_position_mismatch");
|
|
313
153
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
|
|
317
|
-
address: senderIdentity.address,
|
|
318
|
-
})
|
|
319
|
-
? null
|
|
320
|
-
: resolveAnchorOutpointForSender(context.localState.state, senderIdentity.index);
|
|
321
|
-
if (sourceAnchorOutpoint === null
|
|
322
|
-
&& senderIdentity.scriptPubKeyHex !== context.localState.state.funding.scriptPubKeyHex) {
|
|
323
|
-
throw new Error("wallet_anchor_owner_identity_not_supported");
|
|
154
|
+
if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
|
|
155
|
+
throw new Error("wallet_anchor_change_output_mismatch");
|
|
324
156
|
}
|
|
325
|
-
const targetIdentity = selectNextDedicatedIdentityTarget(context.localState.state);
|
|
326
|
-
return {
|
|
327
|
-
readContext: context,
|
|
328
|
-
state: context.localState.state,
|
|
329
|
-
unlockUntilUnixMs: context.localState.unlockUntilUnixMs,
|
|
330
|
-
sourceSender: {
|
|
331
|
-
localIndex: senderIdentity.index,
|
|
332
|
-
scriptPubKeyHex: senderIdentity.scriptPubKeyHex,
|
|
333
|
-
address: senderIdentity.address,
|
|
334
|
-
},
|
|
335
|
-
sourceAnchorOutpoint,
|
|
336
|
-
chainDomain,
|
|
337
|
-
targetIdentity,
|
|
338
|
-
foundingMessageText,
|
|
339
|
-
foundingMessagePayloadHex,
|
|
340
|
-
hadListing: getListing(context.snapshot.state, chainDomain.domainId) !== null,
|
|
341
|
-
};
|
|
342
157
|
}
|
|
343
|
-
function
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
if (domain.name !== options.domainName) {
|
|
347
|
-
return domain;
|
|
348
|
-
}
|
|
158
|
+
function createDraftAnchorMutation(options) {
|
|
159
|
+
const existing = options.existing ?? null;
|
|
160
|
+
if (existing !== null) {
|
|
349
161
|
return {
|
|
350
|
-
...
|
|
351
|
-
|
|
352
|
-
|
|
162
|
+
...existing,
|
|
163
|
+
kind: "anchor",
|
|
164
|
+
domainName: options.domainName,
|
|
165
|
+
parentDomainName: null,
|
|
166
|
+
senderScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
167
|
+
senderLocalIndex: 0,
|
|
168
|
+
intentFingerprintHex: options.intentFingerprintHex,
|
|
169
|
+
status: "draft",
|
|
170
|
+
lastUpdatedAtUnixMs: options.nowUnixMs,
|
|
171
|
+
attemptedTxid: null,
|
|
172
|
+
attemptedWtxid: null,
|
|
173
|
+
...createWalletMutationFeeMetadata(options.feeSelection),
|
|
174
|
+
temporaryBuilderLockedOutpoints: [],
|
|
353
175
|
};
|
|
354
|
-
});
|
|
355
|
-
const nextState = {
|
|
356
|
-
...options.state,
|
|
357
|
-
domains,
|
|
358
|
-
};
|
|
359
|
-
if (family === null) {
|
|
360
|
-
return nextState;
|
|
361
176
|
}
|
|
362
|
-
return upsertProactiveFamily(nextState, {
|
|
363
|
-
...family,
|
|
364
|
-
status: "canceled",
|
|
365
|
-
lastUpdatedAtUnixMs: options.nowUnixMs,
|
|
366
|
-
tx1: family.tx1 == null ? family.tx1 : {
|
|
367
|
-
...family.tx1,
|
|
368
|
-
status: "canceled",
|
|
369
|
-
temporaryBuilderLockedOutpoints: [],
|
|
370
|
-
},
|
|
371
|
-
tx2: family.tx2 == null ? family.tx2 : {
|
|
372
|
-
...family.tx2,
|
|
373
|
-
status: "canceled",
|
|
374
|
-
temporaryBuilderLockedOutpoints: [],
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
function createFamilyTransactionRecord() {
|
|
379
177
|
return {
|
|
178
|
+
mutationId: randomBytes(12).toString("hex"),
|
|
179
|
+
kind: "anchor",
|
|
180
|
+
domainName: options.domainName,
|
|
181
|
+
parentDomainName: null,
|
|
182
|
+
senderScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
183
|
+
senderLocalIndex: 0,
|
|
184
|
+
intentFingerprintHex: options.intentFingerprintHex,
|
|
380
185
|
status: "draft",
|
|
186
|
+
createdAtUnixMs: options.nowUnixMs,
|
|
187
|
+
lastUpdatedAtUnixMs: options.nowUnixMs,
|
|
381
188
|
attemptedTxid: null,
|
|
382
189
|
attemptedWtxid: null,
|
|
190
|
+
...createWalletMutationFeeMetadata(options.feeSelection),
|
|
383
191
|
temporaryBuilderLockedOutpoints: [],
|
|
384
|
-
rawHex: null,
|
|
385
192
|
};
|
|
386
193
|
}
|
|
387
|
-
function
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
]),
|
|
399
|
-
createdAtUnixMs: nowUnixMs,
|
|
400
|
-
lastUpdatedAtUnixMs: nowUnixMs,
|
|
401
|
-
domainName: operation.chainDomain.name,
|
|
402
|
-
domainId: operation.chainDomain.domainId,
|
|
403
|
-
sourceSenderLocalIndex: operation.sourceSender.localIndex,
|
|
404
|
-
sourceSenderScriptPubKeyHex: operation.sourceSender.scriptPubKeyHex,
|
|
405
|
-
reservedDedicatedIndex: operation.targetIdentity.localIndex,
|
|
406
|
-
reservedScriptPubKeyHex: operation.targetIdentity.scriptPubKeyHex,
|
|
407
|
-
foundingMessageText: operation.foundingMessageText,
|
|
408
|
-
foundingMessagePayloadHex: operation.foundingMessagePayloadHex,
|
|
409
|
-
listingCancelCommitted: false,
|
|
410
|
-
currentStep: "reserved",
|
|
411
|
-
tx1: createFamilyTransactionRecord(),
|
|
412
|
-
tx2: createFamilyTransactionRecord(),
|
|
194
|
+
function upsertAnchoredDomainRecord(options) {
|
|
195
|
+
const domains = options.state.domains.slice();
|
|
196
|
+
const existingIndex = domains.findIndex((entry) => entry.name === options.domainName);
|
|
197
|
+
const current = existingIndex >= 0 ? domains[existingIndex] : null;
|
|
198
|
+
const nextRecord = {
|
|
199
|
+
name: options.domainName,
|
|
200
|
+
domainId: options.domainId,
|
|
201
|
+
currentOwnerScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
202
|
+
canonicalChainStatus: "anchored",
|
|
203
|
+
foundingMessageText: options.foundingMessageText ?? current?.foundingMessageText ?? null,
|
|
204
|
+
birthTime: current?.birthTime ?? options.state.lastWrittenAtUnixMs,
|
|
413
205
|
};
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (identities.some((identity) => identity.index === target.localIndex)) {
|
|
417
|
-
return identities;
|
|
206
|
+
if (existingIndex >= 0) {
|
|
207
|
+
domains[existingIndex] = nextRecord;
|
|
418
208
|
}
|
|
419
|
-
|
|
420
|
-
.
|
|
421
|
-
}
|
|
422
|
-
function reserveAnchorFamilyState(state, family, target, foundingMessageText) {
|
|
423
|
-
const domains = state.domains.map((domain) => {
|
|
424
|
-
if (domain.name !== family.domainName) {
|
|
425
|
-
return domain;
|
|
426
|
-
}
|
|
427
|
-
return {
|
|
428
|
-
...domain,
|
|
429
|
-
dedicatedIndex: target.localIndex,
|
|
430
|
-
localAnchorIntent: "reserved",
|
|
431
|
-
foundingMessageText: foundingMessageText ?? domain.foundingMessageText,
|
|
432
|
-
};
|
|
433
|
-
});
|
|
434
|
-
return {
|
|
435
|
-
...upsertProactiveFamily(state, family),
|
|
436
|
-
nextDedicatedIndex: Math.max(state.nextDedicatedIndex, target.localIndex + 1),
|
|
437
|
-
identities: ensureReservedIdentity(state.identities, target),
|
|
438
|
-
domains,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
function updateAnchorFamilyState(options) {
|
|
442
|
-
const nextFamily = {
|
|
443
|
-
...options.family,
|
|
444
|
-
status: options.status,
|
|
445
|
-
currentStep: options.currentStep,
|
|
446
|
-
lastUpdatedAtUnixMs: options.nowUnixMs,
|
|
447
|
-
listingCancelCommitted: options.listingCancelCommitted ?? options.family.listingCancelCommitted,
|
|
448
|
-
tx1: options.tx1 ?? options.family.tx1 ?? createFamilyTransactionRecord(),
|
|
449
|
-
tx2: options.tx2 ?? options.family.tx2 ?? createFamilyTransactionRecord(),
|
|
450
|
-
};
|
|
451
|
-
let identities = ensureReservedIdentity(options.state.identities, options.target);
|
|
452
|
-
if (options.moveOwnershipToTarget) {
|
|
453
|
-
identities = withUpdatedAssignedDomain({
|
|
454
|
-
identities,
|
|
455
|
-
sourceLocalIndex: options.family.sourceSenderLocalIndex ?? null,
|
|
456
|
-
targetLocalIndex: options.target.localIndex,
|
|
457
|
-
domainName: options.family.domainName ?? "",
|
|
458
|
-
});
|
|
209
|
+
else {
|
|
210
|
+
domains.push(nextRecord);
|
|
459
211
|
}
|
|
460
|
-
const domains = options.state.domains.map((domain) => {
|
|
461
|
-
if (domain.name !== options.family.domainName) {
|
|
462
|
-
return domain;
|
|
463
|
-
}
|
|
464
|
-
return {
|
|
465
|
-
...domain,
|
|
466
|
-
dedicatedIndex: options.target.localIndex,
|
|
467
|
-
currentOwnerScriptPubKeyHex: options.moveOwnershipToTarget
|
|
468
|
-
? options.target.scriptPubKeyHex
|
|
469
|
-
: domain.currentOwnerScriptPubKeyHex,
|
|
470
|
-
currentOwnerLocalIndex: options.moveOwnershipToTarget
|
|
471
|
-
? options.target.localIndex
|
|
472
|
-
: domain.currentOwnerLocalIndex,
|
|
473
|
-
localAnchorIntent: options.localAnchorIntent,
|
|
474
|
-
canonicalChainStatus: options.canonicalChainStatus ?? domain.canonicalChainStatus,
|
|
475
|
-
currentCanonicalAnchorOutpoint: options.currentCanonicalAnchorOutpoint ?? domain.currentCanonicalAnchorOutpoint,
|
|
476
|
-
foundingMessageText: options.family.foundingMessageText ?? domain.foundingMessageText,
|
|
477
|
-
};
|
|
478
|
-
});
|
|
479
212
|
return {
|
|
480
|
-
...
|
|
481
|
-
identities,
|
|
213
|
+
...options.state,
|
|
482
214
|
domains,
|
|
483
215
|
};
|
|
484
216
|
}
|
|
485
|
-
function
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
{ data: Buffer.from(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData).toString("hex") },
|
|
490
|
-
{ [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
491
|
-
];
|
|
492
|
-
if (options.operation.sourceAnchorOutpoint === null) {
|
|
493
|
-
return {
|
|
494
|
-
sender: options.operation.sourceSender,
|
|
495
|
-
changeAddress: options.state.funding.address,
|
|
496
|
-
fixedInputs: [],
|
|
497
|
-
outputs,
|
|
498
|
-
changePosition: 2,
|
|
499
|
-
expectedOpReturnScriptHex: encodeOpReturnScript(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData),
|
|
500
|
-
expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
501
|
-
expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
|
|
502
|
-
expectedReplacementAnchorScriptHex: null,
|
|
503
|
-
expectedReplacementAnchorValueSats: null,
|
|
504
|
-
allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
505
|
-
eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
|
|
506
|
-
requiredSenderOutpoint: null,
|
|
507
|
-
requiredProvisionalOutpoint: null,
|
|
508
|
-
errorPrefix: "wallet_anchor_tx1",
|
|
509
|
-
};
|
|
217
|
+
function anchorConfirmedOnSnapshot(options) {
|
|
218
|
+
const chainDomain = lookupDomain(options.snapshot.state, options.domainName);
|
|
219
|
+
if (chainDomain === null || !chainDomain.anchored) {
|
|
220
|
+
return false;
|
|
510
221
|
}
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
&& isSpendableConfirmedUtxo(entry));
|
|
515
|
-
if (sourceAnchor === undefined) {
|
|
516
|
-
throw new Error("wallet_anchor_source_anchor_missing");
|
|
517
|
-
}
|
|
518
|
-
outputs.push({
|
|
519
|
-
[options.operation.sourceSender.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)),
|
|
520
|
-
});
|
|
521
|
-
return {
|
|
522
|
-
sender: options.operation.sourceSender,
|
|
523
|
-
changeAddress: options.state.funding.address,
|
|
524
|
-
fixedInputs: [{ txid: sourceAnchor.txid, vout: sourceAnchor.vout }],
|
|
525
|
-
outputs,
|
|
526
|
-
changePosition: 3,
|
|
527
|
-
expectedOpReturnScriptHex: encodeOpReturnScript(serializeDomainTransfer(options.operation.chainDomain.domainId, Buffer.from(options.operation.targetIdentity.scriptPubKeyHex, "hex")).opReturnData),
|
|
528
|
-
expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
529
|
-
expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
|
|
530
|
-
expectedReplacementAnchorScriptHex: options.operation.sourceSender.scriptPubKeyHex,
|
|
531
|
-
expectedReplacementAnchorValueSats: BigInt(options.state.anchorValueSats),
|
|
532
|
-
allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
533
|
-
eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
|
|
534
|
-
requiredSenderOutpoint: options.operation.sourceAnchorOutpoint,
|
|
535
|
-
requiredProvisionalOutpoint: null,
|
|
536
|
-
errorPrefix: "wallet_anchor_tx1",
|
|
537
|
-
};
|
|
222
|
+
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
223
|
+
return ownerHex === options.state.funding.scriptPubKeyHex
|
|
224
|
+
|| (options.state.localScriptPubKeyHexes ?? []).includes(ownerHex);
|
|
538
225
|
}
|
|
539
|
-
function
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const provisional = options.allUtxos.find((entry) => entry.txid === tx1Txid
|
|
545
|
-
&& entry.vout === 1
|
|
546
|
-
&& entry.scriptPubKey === options.operation.targetIdentity.scriptPubKeyHex
|
|
547
|
-
&& entry.spendable !== false
|
|
548
|
-
&& entry.safe !== false);
|
|
549
|
-
if (provisional === undefined) {
|
|
550
|
-
throw new Error("wallet_anchor_provisional_anchor_missing");
|
|
551
|
-
}
|
|
552
|
-
const fundingUtxos = sortUtxos(options.allUtxos.filter((entry) => entry.scriptPubKey === options.state.funding.scriptPubKeyHex
|
|
553
|
-
&& isSpendableConfirmedUtxo(entry)));
|
|
554
|
-
const tx1FundingChangeInputs = findSpendableFundingInputsFromTransaction({
|
|
555
|
-
allUtxos: options.allUtxos,
|
|
556
|
-
txid: tx1Txid,
|
|
557
|
-
fundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
558
|
-
minConf: 0,
|
|
559
|
-
});
|
|
560
|
-
const foundingPayload = options.operation.foundingMessagePayloadHex === null
|
|
561
|
-
? undefined
|
|
562
|
-
: Buffer.from(options.operation.foundingMessagePayloadHex, "hex");
|
|
563
|
-
const opReturnData = serializeDomainAnchor(options.operation.chainDomain.domainId, foundingPayload).opReturnData;
|
|
564
|
-
return {
|
|
565
|
-
sender: {
|
|
566
|
-
localIndex: options.operation.targetIdentity.localIndex,
|
|
567
|
-
scriptPubKeyHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
568
|
-
address: options.operation.targetIdentity.address,
|
|
569
|
-
},
|
|
570
|
-
changeAddress: options.state.funding.address,
|
|
571
|
-
fixedInputs: [
|
|
572
|
-
{ txid: provisional.txid, vout: provisional.vout },
|
|
573
|
-
...tx1FundingChangeInputs,
|
|
574
|
-
],
|
|
575
|
-
outputs: [
|
|
576
|
-
{ data: Buffer.from(opReturnData).toString("hex") },
|
|
577
|
-
{ [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
578
|
-
],
|
|
579
|
-
changePosition: 2,
|
|
580
|
-
expectedOpReturnScriptHex: encodeOpReturnScript(opReturnData),
|
|
581
|
-
expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
582
|
-
expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
|
|
583
|
-
expectedReplacementAnchorScriptHex: null,
|
|
584
|
-
expectedReplacementAnchorValueSats: null,
|
|
585
|
-
allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
586
|
-
eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
|
|
587
|
-
requiredSenderOutpoint: null,
|
|
588
|
-
requiredProvisionalOutpoint: {
|
|
589
|
-
txid: provisional.txid,
|
|
590
|
-
vout: provisional.vout,
|
|
591
|
-
},
|
|
592
|
-
errorPrefix: "wallet_anchor_tx2",
|
|
226
|
+
async function saveState(options) {
|
|
227
|
+
const nextState = {
|
|
228
|
+
...options.state,
|
|
229
|
+
stateRevision: options.state.stateRevision + 1,
|
|
230
|
+
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
593
231
|
};
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
600
|
-
}
|
|
601
|
-
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
602
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
|
|
603
|
-
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex) {
|
|
604
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
605
|
-
}
|
|
606
|
-
if (plan.requiredSenderOutpoint !== null) {
|
|
607
|
-
if (!inputMatchesOutpoint(inputs[0], plan.requiredSenderOutpoint)) {
|
|
608
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
assertFundingInputsAfterFixedPrefix({
|
|
612
|
-
decoded,
|
|
613
|
-
fixedInputs: plan.fixedInputs,
|
|
614
|
-
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
615
|
-
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
616
|
-
errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
|
|
232
|
+
await saveWalletStatePreservingUnlock({
|
|
233
|
+
state: nextState,
|
|
234
|
+
provider: options.provider,
|
|
235
|
+
nowUnixMs: options.nowUnixMs,
|
|
236
|
+
paths: options.paths,
|
|
617
237
|
});
|
|
618
|
-
|
|
619
|
-
throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
|
|
620
|
-
}
|
|
621
|
-
if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
|
|
622
|
-
throw new Error(`${plan.errorPrefix}_provisional_anchor_output_mismatch`);
|
|
623
|
-
}
|
|
624
|
-
if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
|
|
625
|
-
throw new Error(`${plan.errorPrefix}_provisional_anchor_value_mismatch`);
|
|
626
|
-
}
|
|
627
|
-
const expectedWithoutChange = plan.expectedReplacementAnchorScriptHex === null ? 2 : 3;
|
|
628
|
-
if (plan.expectedReplacementAnchorScriptHex !== null) {
|
|
629
|
-
if (outputs[2]?.scriptPubKey?.hex !== plan.expectedReplacementAnchorScriptHex) {
|
|
630
|
-
throw new Error(`${plan.errorPrefix}_replacement_anchor_output_mismatch`);
|
|
631
|
-
}
|
|
632
|
-
if (valueToSats(outputs[2]?.value ?? 0) !== (plan.expectedReplacementAnchorValueSats ?? 0n)) {
|
|
633
|
-
throw new Error(`${plan.errorPrefix}_replacement_anchor_value_mismatch`);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
if (funded.changepos === -1) {
|
|
637
|
-
if (outputs.length !== expectedWithoutChange) {
|
|
638
|
-
throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
|
|
639
|
-
}
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
|
|
643
|
-
throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
|
|
644
|
-
}
|
|
645
|
-
if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
|
|
646
|
-
throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
|
|
647
|
-
}
|
|
238
|
+
return nextState;
|
|
648
239
|
}
|
|
649
|
-
function
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(decoded, 0);
|
|
657
|
-
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex
|
|
658
|
-
|| !inputMatchesOutpoint(inputs[0], plan.requiredProvisionalOutpoint)) {
|
|
659
|
-
throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
|
|
660
|
-
}
|
|
661
|
-
assertFundingInputsAfterFixedPrefix({
|
|
662
|
-
decoded,
|
|
663
|
-
fixedInputs: plan.fixedInputs,
|
|
664
|
-
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
665
|
-
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
666
|
-
errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
|
|
667
|
-
});
|
|
668
|
-
if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
|
|
669
|
-
throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
|
|
670
|
-
}
|
|
671
|
-
if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
|
|
672
|
-
throw new Error(`${plan.errorPrefix}_canonical_anchor_output_mismatch`);
|
|
673
|
-
}
|
|
674
|
-
if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
|
|
675
|
-
throw new Error(`${plan.errorPrefix}_canonical_anchor_value_mismatch`);
|
|
676
|
-
}
|
|
677
|
-
const expectedWithoutChange = 2;
|
|
678
|
-
if (funded.changepos === -1) {
|
|
679
|
-
if (outputs.length !== expectedWithoutChange) {
|
|
680
|
-
throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
|
|
681
|
-
}
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
|
|
685
|
-
throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
|
|
686
|
-
}
|
|
687
|
-
if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
|
|
688
|
-
throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
|
|
240
|
+
async function reconcilePendingAnchorMutation(options) {
|
|
241
|
+
if (options.mutation.status === "repair-required") {
|
|
242
|
+
return {
|
|
243
|
+
state: options.state,
|
|
244
|
+
mutation: options.mutation,
|
|
245
|
+
resolution: "repair-required",
|
|
246
|
+
};
|
|
689
247
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
return buildWalletMutationTransactionWithReserveFallback({
|
|
693
|
-
rpc: options.rpc,
|
|
694
|
-
walletName: options.walletName,
|
|
248
|
+
if (options.context.snapshot !== null && anchorConfirmedOnSnapshot({
|
|
249
|
+
snapshot: options.context.snapshot,
|
|
695
250
|
state: options.state,
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
mempoolRejectPrefix: "wallet_anchor_tx2_mempool_rejected",
|
|
712
|
-
availableFundingMinConf: 0,
|
|
713
|
-
reserveCandidates: options.state.proactiveReserveOutpoints,
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
async function relockAnchorOutpoint(rpc, walletName, outpoint) {
|
|
717
|
-
if (outpoint === null) {
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
await rpc.lockUnspent(walletName, false, [outpoint]).catch(() => undefined);
|
|
721
|
-
}
|
|
722
|
-
function resolveAcceptedFamilyStatus(options) {
|
|
723
|
-
const chainDomain = options.snapshot === null || options.family.domainName == null
|
|
724
|
-
? null
|
|
725
|
-
: lookupDomain(options.snapshot.state, options.family.domainName);
|
|
726
|
-
if (chainDomain === null) {
|
|
727
|
-
return "live";
|
|
728
|
-
}
|
|
729
|
-
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
730
|
-
return chainDomain.anchored && ownerHex === options.target.scriptPubKeyHex
|
|
731
|
-
? "confirmed"
|
|
732
|
-
: "live";
|
|
733
|
-
}
|
|
734
|
-
async function reconcileAnchorFamily(options) {
|
|
735
|
-
const chainDomain = lookupDomain(options.operation.readContext.snapshot.state, options.operation.chainDomain.name);
|
|
736
|
-
const targetScript = options.operation.targetIdentity.scriptPubKeyHex;
|
|
737
|
-
if (chainDomain !== null) {
|
|
738
|
-
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
739
|
-
if (chainDomain.anchored && ownerHex === targetScript) {
|
|
740
|
-
const nextState = updateAnchorFamilyState({
|
|
741
|
-
state: options.state,
|
|
742
|
-
family: options.family,
|
|
743
|
-
target: options.operation.targetIdentity,
|
|
744
|
-
status: "confirmed",
|
|
745
|
-
localAnchorIntent: "none",
|
|
746
|
-
currentStep: "tx2",
|
|
747
|
-
nowUnixMs: options.nowUnixMs,
|
|
748
|
-
tx1: options.family.tx1 == null ? undefined : { ...options.family.tx1, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
|
|
749
|
-
tx2: options.family.tx2 == null ? undefined : { ...options.family.tx2, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
|
|
750
|
-
moveOwnershipToTarget: true,
|
|
751
|
-
canonicalChainStatus: "anchored",
|
|
752
|
-
currentCanonicalAnchorOutpoint: options.family.tx2?.attemptedTxid == null
|
|
753
|
-
? options.state.domains.find((domain) => domain.name === options.family.domainName)?.currentCanonicalAnchorOutpoint ?? null
|
|
754
|
-
: {
|
|
755
|
-
txid: options.family.tx2.attemptedTxid,
|
|
756
|
-
vout: 1,
|
|
757
|
-
valueSats: options.state.anchorValueSats,
|
|
758
|
-
},
|
|
759
|
-
});
|
|
760
|
-
await saveWalletStatePreservingUnlock({
|
|
761
|
-
state: {
|
|
762
|
-
...nextState,
|
|
763
|
-
stateRevision: nextState.stateRevision + 1,
|
|
764
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
765
|
-
},
|
|
766
|
-
provider: options.provider,
|
|
767
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
768
|
-
nowUnixMs: options.nowUnixMs,
|
|
769
|
-
paths: options.paths,
|
|
770
|
-
});
|
|
771
|
-
return {
|
|
251
|
+
domainName: options.mutation.domainName,
|
|
252
|
+
})) {
|
|
253
|
+
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
|
|
254
|
+
const confirmedMutation = updateMutationRecord(options.mutation, "confirmed", options.nowUnixMs, {
|
|
255
|
+
temporaryBuilderLockedOutpoints: [],
|
|
256
|
+
});
|
|
257
|
+
const chainDomain = lookupDomain(options.context.snapshot.state, options.mutation.domainName);
|
|
258
|
+
const nextState = upsertAnchoredDomainRecord({
|
|
259
|
+
state: upsertPendingMutation(options.state, confirmedMutation),
|
|
260
|
+
domainName: options.mutation.domainName,
|
|
261
|
+
domainId: chainDomain?.domainId ?? 0,
|
|
262
|
+
foundingMessageText: options.foundingMessageText,
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
state: await saveState({
|
|
772
266
|
state: nextState,
|
|
773
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
774
|
-
...options.family,
|
|
775
|
-
status: "confirmed",
|
|
776
|
-
},
|
|
777
|
-
resolution: "confirmed",
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
if (ownerHex === targetScript && !chainDomain.anchored) {
|
|
781
|
-
const nextState = updateAnchorFamilyState({
|
|
782
|
-
state: options.state,
|
|
783
|
-
family: options.family,
|
|
784
|
-
target: options.operation.targetIdentity,
|
|
785
|
-
status: "repair-required",
|
|
786
|
-
localAnchorIntent: "repair-required",
|
|
787
|
-
currentStep: "tx2",
|
|
788
|
-
nowUnixMs: options.nowUnixMs,
|
|
789
|
-
listingCancelCommitted: true,
|
|
790
|
-
moveOwnershipToTarget: true,
|
|
791
|
-
});
|
|
792
|
-
await saveWalletStatePreservingUnlock({
|
|
793
|
-
state: {
|
|
794
|
-
...nextState,
|
|
795
|
-
stateRevision: nextState.stateRevision + 1,
|
|
796
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
797
|
-
},
|
|
798
267
|
provider: options.provider,
|
|
799
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
800
268
|
nowUnixMs: options.nowUnixMs,
|
|
801
269
|
paths: options.paths,
|
|
802
|
-
})
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
806
|
-
...options.family,
|
|
807
|
-
status: "repair-required",
|
|
808
|
-
},
|
|
809
|
-
resolution: "repair-required",
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
const mempool = await options.rpc.getRawMempool().catch(() => []);
|
|
814
|
-
if (options.family.tx2?.attemptedTxid != null && mempool.includes(options.family.tx2.attemptedTxid)) {
|
|
815
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2.temporaryBuilderLockedOutpoints);
|
|
816
|
-
const nextState = updateAnchorFamilyState({
|
|
817
|
-
state: options.state,
|
|
818
|
-
family: options.family,
|
|
819
|
-
target: options.operation.targetIdentity,
|
|
820
|
-
status: "live",
|
|
821
|
-
localAnchorIntent: "tx2-live",
|
|
822
|
-
currentStep: "tx2",
|
|
823
|
-
nowUnixMs: options.nowUnixMs,
|
|
824
|
-
tx2: {
|
|
825
|
-
...options.family.tx2,
|
|
826
|
-
status: "live",
|
|
827
|
-
temporaryBuilderLockedOutpoints: [],
|
|
828
|
-
},
|
|
829
|
-
listingCancelCommitted: true,
|
|
830
|
-
moveOwnershipToTarget: true,
|
|
831
|
-
currentCanonicalAnchorOutpoint: {
|
|
832
|
-
txid: options.family.tx2.attemptedTxid,
|
|
833
|
-
vout: 1,
|
|
834
|
-
valueSats: options.state.anchorValueSats,
|
|
835
|
-
},
|
|
836
|
-
});
|
|
837
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
838
|
-
txid: options.family.tx2.attemptedTxid,
|
|
839
|
-
vout: 1,
|
|
840
|
-
});
|
|
841
|
-
await saveWalletStatePreservingUnlock({
|
|
842
|
-
state: {
|
|
843
|
-
...nextState,
|
|
844
|
-
stateRevision: nextState.stateRevision + 1,
|
|
845
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
846
|
-
},
|
|
847
|
-
provider: options.provider,
|
|
848
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
849
|
-
nowUnixMs: options.nowUnixMs,
|
|
850
|
-
paths: options.paths,
|
|
851
|
-
});
|
|
852
|
-
return {
|
|
853
|
-
state: nextState,
|
|
854
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
855
|
-
...options.family,
|
|
856
|
-
status: "live",
|
|
857
|
-
},
|
|
858
|
-
resolution: "live",
|
|
270
|
+
}),
|
|
271
|
+
mutation: confirmedMutation,
|
|
272
|
+
resolution: "confirmed",
|
|
859
273
|
};
|
|
860
274
|
}
|
|
861
|
-
if (options.
|
|
862
|
-
await
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
target: options.operation.targetIdentity,
|
|
867
|
-
status: "live",
|
|
868
|
-
localAnchorIntent: "tx1-live",
|
|
869
|
-
currentStep: "tx1",
|
|
870
|
-
nowUnixMs: options.nowUnixMs,
|
|
871
|
-
tx1: {
|
|
872
|
-
...options.family.tx1,
|
|
873
|
-
status: "live",
|
|
275
|
+
if (options.mutation.attemptedTxid !== null) {
|
|
276
|
+
const mempool = await options.rpc.getRawMempool().catch(() => []);
|
|
277
|
+
if (mempool.includes(options.mutation.attemptedTxid)) {
|
|
278
|
+
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
|
|
279
|
+
const liveMutation = updateMutationRecord(options.mutation, "live", options.nowUnixMs, {
|
|
874
280
|
temporaryBuilderLockedOutpoints: [],
|
|
875
|
-
},
|
|
876
|
-
listingCancelCommitted: options.operation.hadListing,
|
|
877
|
-
moveOwnershipToTarget: true,
|
|
878
|
-
});
|
|
879
|
-
if (options.operation.sourceAnchorOutpoint !== null) {
|
|
880
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
881
|
-
txid: options.family.tx1.attemptedTxid,
|
|
882
|
-
vout: 2,
|
|
883
281
|
});
|
|
282
|
+
const domainId = (options.context.snapshot === null
|
|
283
|
+
? null
|
|
284
|
+
: lookupDomain(options.context.snapshot.state, options.mutation.domainName)?.domainId)
|
|
285
|
+
?? options.state.domains.find((domain) => domain.name === options.mutation.domainName)?.domainId
|
|
286
|
+
?? 0;
|
|
287
|
+
const nextState = upsertAnchoredDomainRecord({
|
|
288
|
+
state: upsertPendingMutation(options.state, liveMutation),
|
|
289
|
+
domainName: options.mutation.domainName,
|
|
290
|
+
domainId,
|
|
291
|
+
foundingMessageText: options.foundingMessageText,
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
state: await saveState({
|
|
295
|
+
state: nextState,
|
|
296
|
+
provider: options.provider,
|
|
297
|
+
nowUnixMs: options.nowUnixMs,
|
|
298
|
+
paths: options.paths,
|
|
299
|
+
}),
|
|
300
|
+
mutation: liveMutation,
|
|
301
|
+
resolution: "live",
|
|
302
|
+
};
|
|
884
303
|
}
|
|
885
|
-
await saveWalletStatePreservingUnlock({
|
|
886
|
-
state: {
|
|
887
|
-
...nextState,
|
|
888
|
-
stateRevision: nextState.stateRevision + 1,
|
|
889
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
890
|
-
},
|
|
891
|
-
provider: options.provider,
|
|
892
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
893
|
-
nowUnixMs: options.nowUnixMs,
|
|
894
|
-
paths: options.paths,
|
|
895
|
-
});
|
|
896
|
-
return {
|
|
897
|
-
state: nextState,
|
|
898
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
899
|
-
...options.family,
|
|
900
|
-
status: "live",
|
|
901
|
-
},
|
|
902
|
-
resolution: "ready-for-tx2",
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
if (options.family.currentStep === "tx2" || options.family.tx2?.attemptedTxid != null) {
|
|
906
|
-
const nextState = updateAnchorFamilyState({
|
|
907
|
-
state: options.state,
|
|
908
|
-
family: options.family,
|
|
909
|
-
target: options.operation.targetIdentity,
|
|
910
|
-
status: "repair-required",
|
|
911
|
-
localAnchorIntent: "repair-required",
|
|
912
|
-
currentStep: "tx2",
|
|
913
|
-
nowUnixMs: options.nowUnixMs,
|
|
914
|
-
listingCancelCommitted: true,
|
|
915
|
-
moveOwnershipToTarget: true,
|
|
916
|
-
});
|
|
917
|
-
await saveWalletStatePreservingUnlock({
|
|
918
|
-
state: {
|
|
919
|
-
...nextState,
|
|
920
|
-
stateRevision: nextState.stateRevision + 1,
|
|
921
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
922
|
-
},
|
|
923
|
-
provider: options.provider,
|
|
924
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
925
|
-
nowUnixMs: options.nowUnixMs,
|
|
926
|
-
paths: options.paths,
|
|
927
|
-
});
|
|
928
|
-
return {
|
|
929
|
-
state: nextState,
|
|
930
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
931
|
-
...options.family,
|
|
932
|
-
status: "repair-required",
|
|
933
|
-
},
|
|
934
|
-
resolution: "repair-required",
|
|
935
|
-
};
|
|
936
304
|
}
|
|
937
|
-
if (
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
currentStep: options.family.currentStep,
|
|
945
|
-
nowUnixMs: options.nowUnixMs,
|
|
946
|
-
tx1: options.family.tx1 == null ? undefined : {
|
|
947
|
-
...options.family.tx1,
|
|
948
|
-
status: "canceled",
|
|
949
|
-
temporaryBuilderLockedOutpoints: [],
|
|
950
|
-
},
|
|
951
|
-
tx2: options.family.tx2 == null ? undefined : {
|
|
952
|
-
...options.family.tx2,
|
|
953
|
-
status: "canceled",
|
|
954
|
-
temporaryBuilderLockedOutpoints: [],
|
|
955
|
-
},
|
|
956
|
-
});
|
|
957
|
-
await saveWalletStatePreservingUnlock({
|
|
958
|
-
state: {
|
|
959
|
-
...nextState,
|
|
960
|
-
stateRevision: nextState.stateRevision + 1,
|
|
961
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
962
|
-
},
|
|
963
|
-
provider: options.provider,
|
|
964
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
965
|
-
nowUnixMs: options.nowUnixMs,
|
|
966
|
-
paths: options.paths,
|
|
305
|
+
if (options.mutation.status === "broadcast-unknown"
|
|
306
|
+
|| options.mutation.status === "live"
|
|
307
|
+
|| options.mutation.status === "draft"
|
|
308
|
+
|| options.mutation.status === "broadcasting") {
|
|
309
|
+
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.mutation.temporaryBuilderLockedOutpoints);
|
|
310
|
+
const canceledMutation = updateMutationRecord(options.mutation, "canceled", options.nowUnixMs, {
|
|
311
|
+
temporaryBuilderLockedOutpoints: [],
|
|
967
312
|
});
|
|
313
|
+
const nextState = upsertPendingMutation(options.state, canceledMutation);
|
|
968
314
|
return {
|
|
969
|
-
state:
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
315
|
+
state: await saveState({
|
|
316
|
+
state: nextState,
|
|
317
|
+
provider: options.provider,
|
|
318
|
+
nowUnixMs: options.nowUnixMs,
|
|
319
|
+
paths: options.paths,
|
|
320
|
+
}),
|
|
321
|
+
mutation: canceledMutation,
|
|
974
322
|
resolution: "not-seen",
|
|
975
323
|
};
|
|
976
324
|
}
|
|
977
325
|
return {
|
|
978
326
|
state: options.state,
|
|
979
|
-
|
|
327
|
+
mutation: options.mutation,
|
|
980
328
|
resolution: "continue",
|
|
981
329
|
};
|
|
982
330
|
}
|
|
983
|
-
function createBroadcastingTxRecord(built) {
|
|
984
|
-
return {
|
|
985
|
-
status: "broadcasting",
|
|
986
|
-
attemptedTxid: built.txid,
|
|
987
|
-
attemptedWtxid: built.wtxid,
|
|
988
|
-
temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
|
|
989
|
-
rawHex: built.rawHex,
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
async function saveState(state, provider, unlockUntilUnixMs, nowUnixMs, paths) {
|
|
993
|
-
const nextState = {
|
|
994
|
-
...state,
|
|
995
|
-
stateRevision: state.stateRevision + 1,
|
|
996
|
-
lastWrittenAtUnixMs: nowUnixMs,
|
|
997
|
-
};
|
|
998
|
-
await saveWalletStatePreservingUnlock({
|
|
999
|
-
state: nextState,
|
|
1000
|
-
provider,
|
|
1001
|
-
unlockUntilUnixMs,
|
|
1002
|
-
nowUnixMs,
|
|
1003
|
-
paths,
|
|
1004
|
-
});
|
|
1005
|
-
return nextState;
|
|
1006
|
-
}
|
|
1007
|
-
async function submitTx2(options) {
|
|
1008
|
-
let nextState = options.state;
|
|
1009
|
-
let family = options.family;
|
|
1010
|
-
const tx2Plan = buildTx2Plan({
|
|
1011
|
-
state: nextState,
|
|
1012
|
-
allUtxos: await options.rpc.listUnspent(options.walletName, 0),
|
|
1013
|
-
operation: options.operation,
|
|
1014
|
-
family,
|
|
1015
|
-
});
|
|
1016
|
-
const builtTx2 = await buildTx2({
|
|
1017
|
-
rpc: options.rpc,
|
|
1018
|
-
walletName: options.walletName,
|
|
1019
|
-
state: nextState,
|
|
1020
|
-
plan: tx2Plan,
|
|
1021
|
-
});
|
|
1022
|
-
const broadcastingTx2 = createBroadcastingTxRecord(builtTx2);
|
|
1023
|
-
family = {
|
|
1024
|
-
...family,
|
|
1025
|
-
status: "broadcasting",
|
|
1026
|
-
currentStep: "tx2",
|
|
1027
|
-
tx2: broadcastingTx2,
|
|
1028
|
-
};
|
|
1029
|
-
nextState = updateAnchorFamilyState({
|
|
1030
|
-
state: nextState,
|
|
1031
|
-
family,
|
|
1032
|
-
target: options.operation.targetIdentity,
|
|
1033
|
-
status: "broadcasting",
|
|
1034
|
-
localAnchorIntent: "tx1-live",
|
|
1035
|
-
currentStep: "tx2",
|
|
1036
|
-
nowUnixMs: options.nowUnixMs,
|
|
1037
|
-
tx2: broadcastingTx2,
|
|
1038
|
-
listingCancelCommitted: true,
|
|
1039
|
-
moveOwnershipToTarget: true,
|
|
1040
|
-
});
|
|
1041
|
-
nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1042
|
-
ensureSameTipHeight(options.readContext, (await options.rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
|
|
1043
|
-
try {
|
|
1044
|
-
await options.rpc.sendRawTransaction(builtTx2.rawHex);
|
|
1045
|
-
}
|
|
1046
|
-
catch (error) {
|
|
1047
|
-
if (!isAlreadyAcceptedError(error)) {
|
|
1048
|
-
if (isBroadcastUnknownError(error)) {
|
|
1049
|
-
family = {
|
|
1050
|
-
...family,
|
|
1051
|
-
status: "broadcast-unknown",
|
|
1052
|
-
tx2: {
|
|
1053
|
-
...broadcastingTx2,
|
|
1054
|
-
status: "broadcast-unknown",
|
|
1055
|
-
},
|
|
1056
|
-
};
|
|
1057
|
-
nextState = updateAnchorFamilyState({
|
|
1058
|
-
state: nextState,
|
|
1059
|
-
family,
|
|
1060
|
-
target: options.operation.targetIdentity,
|
|
1061
|
-
status: "broadcast-unknown",
|
|
1062
|
-
localAnchorIntent: "tx1-live",
|
|
1063
|
-
currentStep: "tx2",
|
|
1064
|
-
nowUnixMs: options.nowUnixMs,
|
|
1065
|
-
tx2: family.tx2,
|
|
1066
|
-
listingCancelCommitted: true,
|
|
1067
|
-
moveOwnershipToTarget: true,
|
|
1068
|
-
});
|
|
1069
|
-
await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1070
|
-
throw new Error("wallet_anchor_tx2_broadcast_unknown");
|
|
1071
|
-
}
|
|
1072
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
|
|
1073
|
-
family = {
|
|
1074
|
-
...family,
|
|
1075
|
-
status: "repair-required",
|
|
1076
|
-
tx2: {
|
|
1077
|
-
...broadcastingTx2,
|
|
1078
|
-
status: "repair-required",
|
|
1079
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1080
|
-
},
|
|
1081
|
-
};
|
|
1082
|
-
nextState = updateAnchorFamilyState({
|
|
1083
|
-
state: nextState,
|
|
1084
|
-
family,
|
|
1085
|
-
target: options.operation.targetIdentity,
|
|
1086
|
-
status: "repair-required",
|
|
1087
|
-
localAnchorIntent: "repair-required",
|
|
1088
|
-
currentStep: "tx2",
|
|
1089
|
-
nowUnixMs: options.nowUnixMs,
|
|
1090
|
-
tx2: family.tx2,
|
|
1091
|
-
listingCancelCommitted: true,
|
|
1092
|
-
moveOwnershipToTarget: true,
|
|
1093
|
-
});
|
|
1094
|
-
await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1095
|
-
throw error;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
|
|
1099
|
-
const finalStatus = resolveAcceptedFamilyStatus({
|
|
1100
|
-
snapshot: options.readContext.snapshot,
|
|
1101
|
-
family,
|
|
1102
|
-
target: options.operation.targetIdentity,
|
|
1103
|
-
});
|
|
1104
|
-
family = {
|
|
1105
|
-
...family,
|
|
1106
|
-
status: finalStatus,
|
|
1107
|
-
currentStep: "tx2",
|
|
1108
|
-
tx2: {
|
|
1109
|
-
...broadcastingTx2,
|
|
1110
|
-
status: finalStatus,
|
|
1111
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1112
|
-
},
|
|
1113
|
-
};
|
|
1114
|
-
nextState = updateAnchorFamilyState({
|
|
1115
|
-
state: nextState,
|
|
1116
|
-
family,
|
|
1117
|
-
target: options.operation.targetIdentity,
|
|
1118
|
-
status: finalStatus,
|
|
1119
|
-
localAnchorIntent: finalStatus === "confirmed" ? "none" : "tx2-live",
|
|
1120
|
-
currentStep: "tx2",
|
|
1121
|
-
nowUnixMs: options.nowUnixMs,
|
|
1122
|
-
tx2: family.tx2,
|
|
1123
|
-
listingCancelCommitted: true,
|
|
1124
|
-
moveOwnershipToTarget: true,
|
|
1125
|
-
canonicalChainStatus: finalStatus === "confirmed" ? "anchored" : undefined,
|
|
1126
|
-
currentCanonicalAnchorOutpoint: {
|
|
1127
|
-
txid: builtTx2.txid,
|
|
1128
|
-
vout: 1,
|
|
1129
|
-
valueSats: nextState.anchorValueSats,
|
|
1130
|
-
},
|
|
1131
|
-
});
|
|
1132
|
-
nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1133
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
1134
|
-
txid: builtTx2.txid,
|
|
1135
|
-
vout: 1,
|
|
1136
|
-
});
|
|
1137
|
-
return {
|
|
1138
|
-
domainName: options.operation.chainDomain.name,
|
|
1139
|
-
txid: builtTx2.txid,
|
|
1140
|
-
tx1Txid: family.tx1?.attemptedTxid ?? "unknown",
|
|
1141
|
-
tx2Txid: builtTx2.txid,
|
|
1142
|
-
dedicatedIndex: options.operation.targetIdentity.localIndex,
|
|
1143
|
-
status: finalStatus,
|
|
1144
|
-
reusedExisting: false,
|
|
1145
|
-
foundingMessageText: options.operation.foundingMessageText,
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
331
|
function ensureSameTipHeight(context, bestHeight, errorCode) {
|
|
1149
332
|
if (context.snapshot?.tip?.height !== bestHeight) {
|
|
1150
333
|
throw new Error(errorCode);
|
|
@@ -1157,21 +340,16 @@ export async function anchorDomain(options) {
|
|
|
1157
340
|
const provider = options.provider ?? createDefaultWalletSecretProvider();
|
|
1158
341
|
const nowUnixMs = options.nowUnixMs ?? Date.now();
|
|
1159
342
|
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
343
|
+
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1160
344
|
const controlLock = await acquireFileLock(paths.walletControlLockPath, {
|
|
1161
345
|
purpose: "wallet-anchor",
|
|
1162
346
|
walletRootId: null,
|
|
1163
347
|
});
|
|
1164
|
-
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1165
348
|
try {
|
|
1166
349
|
const miningPreemption = await pauseMiningForWalletMutation({
|
|
1167
350
|
paths,
|
|
1168
351
|
reason: "wallet-anchor",
|
|
1169
352
|
});
|
|
1170
|
-
const message = await resolveFoundingMessage({
|
|
1171
|
-
foundingMessageText: options.foundingMessageText,
|
|
1172
|
-
promptForFoundingMessageWhenMissing: options.promptForFoundingMessageWhenMissing,
|
|
1173
|
-
prompter: options.prompter,
|
|
1174
|
-
});
|
|
1175
353
|
const readContext = await (options.openReadContext ?? openWalletReadContext)({
|
|
1176
354
|
dataDir: options.dataDir,
|
|
1177
355
|
databasePath: options.databasePath,
|
|
@@ -1180,301 +358,222 @@ export async function anchorDomain(options) {
|
|
|
1180
358
|
paths,
|
|
1181
359
|
});
|
|
1182
360
|
try {
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
361
|
+
assertWalletMutationContextReady(readContext, "wallet_anchor");
|
|
362
|
+
const message = await resolveFoundingMessage({
|
|
363
|
+
foundingMessageText: options.foundingMessageText,
|
|
364
|
+
promptForFoundingMessageWhenMissing: options.promptForFoundingMessageWhenMissing,
|
|
365
|
+
prompter: options.prompter,
|
|
366
|
+
});
|
|
367
|
+
const state = readContext.localState.state;
|
|
368
|
+
const chainDomain = lookupDomain(readContext.snapshot.state, normalizedDomainName);
|
|
369
|
+
if (chainDomain === null) {
|
|
370
|
+
throw new Error("wallet_anchor_domain_not_found");
|
|
371
|
+
}
|
|
372
|
+
if (chainDomain.anchored) {
|
|
373
|
+
throw new Error("wallet_anchor_domain_already_anchored");
|
|
374
|
+
}
|
|
375
|
+
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
376
|
+
const localScriptHexes = new Set([
|
|
377
|
+
state.funding.scriptPubKeyHex,
|
|
378
|
+
...(state.localScriptPubKeyHexes ?? []),
|
|
379
|
+
]);
|
|
380
|
+
if (!localScriptHexes.has(ownerHex)) {
|
|
381
|
+
throw new Error("wallet_anchor_owner_not_locally_controlled");
|
|
1189
382
|
}
|
|
1190
|
-
if (
|
|
1191
|
-
throw new Error("
|
|
383
|
+
if (state.funding.address.trim() === "") {
|
|
384
|
+
throw new Error("wallet_anchor_owner_identity_not_supported");
|
|
1192
385
|
}
|
|
386
|
+
const intentFingerprintHex = createIntentFingerprint([
|
|
387
|
+
"anchor",
|
|
388
|
+
state.walletRootId,
|
|
389
|
+
normalizedDomainName,
|
|
390
|
+
state.funding.scriptPubKeyHex,
|
|
391
|
+
message.payloadHex ?? "",
|
|
392
|
+
]);
|
|
1193
393
|
const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
|
|
1194
394
|
dataDir: options.dataDir,
|
|
1195
395
|
chain: "main",
|
|
1196
396
|
startHeight: 0,
|
|
1197
|
-
walletRootId:
|
|
397
|
+
walletRootId: state.walletRootId,
|
|
1198
398
|
});
|
|
1199
399
|
const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
|
|
1200
|
-
const walletName =
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
targetIdentity: existingTargetIdentity,
|
|
1213
|
-
},
|
|
400
|
+
const walletName = state.managedCoreWallet.walletName;
|
|
401
|
+
const feeSelection = await resolveWalletMutationFeeSelection({
|
|
402
|
+
rpc,
|
|
403
|
+
feeRateSatVb: options.feeRateSatVb ?? null,
|
|
404
|
+
});
|
|
405
|
+
const existingMutation = findPendingMutationByIntent(state, intentFingerprintHex);
|
|
406
|
+
let workingState = state;
|
|
407
|
+
let replacementFixedInputs = null;
|
|
408
|
+
if (existingMutation !== null) {
|
|
409
|
+
const reconciled = await reconcilePendingAnchorMutation({
|
|
410
|
+
state,
|
|
411
|
+
mutation: existingMutation,
|
|
1214
412
|
provider,
|
|
1215
413
|
nowUnixMs,
|
|
1216
414
|
paths,
|
|
1217
|
-
unlockUntilUnixMs: operation.unlockUntilUnixMs,
|
|
1218
415
|
rpc,
|
|
1219
416
|
walletName,
|
|
417
|
+
context: readContext,
|
|
418
|
+
foundingMessageText: message.text,
|
|
1220
419
|
});
|
|
1221
420
|
workingState = reconciled.state;
|
|
1222
421
|
if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
if (reconciled.resolution === "ready-for-tx2") {
|
|
1238
|
-
operation = {
|
|
1239
|
-
...operation,
|
|
1240
|
-
targetIdentity: existingTargetIdentity,
|
|
1241
|
-
};
|
|
1242
|
-
resumedFamily = reconciled.family;
|
|
1243
|
-
resumedExisting = true;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
let nextState = workingState;
|
|
1247
|
-
let family;
|
|
1248
|
-
if (resumedFamily !== null) {
|
|
1249
|
-
family = resumedFamily;
|
|
1250
|
-
}
|
|
1251
|
-
else {
|
|
1252
|
-
await confirmAnchor(options.prompter, operation);
|
|
1253
|
-
nextState = reserveAnchorFamilyState(nextState, initialFamily, operation.targetIdentity, operation.foundingMessageText);
|
|
1254
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1255
|
-
const tx1Plan = buildTx1Plan({
|
|
1256
|
-
state: nextState,
|
|
1257
|
-
allUtxos: await rpc.listUnspent(walletName, 1),
|
|
1258
|
-
operation,
|
|
1259
|
-
});
|
|
1260
|
-
const builtTx1 = await buildTx1({
|
|
1261
|
-
rpc,
|
|
1262
|
-
walletName,
|
|
1263
|
-
state: nextState,
|
|
1264
|
-
plan: tx1Plan,
|
|
1265
|
-
});
|
|
1266
|
-
const broadcastingTx1 = createBroadcastingTxRecord(builtTx1);
|
|
1267
|
-
family = {
|
|
1268
|
-
...(findAnchorFamilyByIntent(nextState, initialFamily.intentFingerprintHex) ?? initialFamily),
|
|
1269
|
-
status: "broadcasting",
|
|
1270
|
-
currentStep: "tx1",
|
|
1271
|
-
lastUpdatedAtUnixMs: nowUnixMs,
|
|
1272
|
-
tx1: broadcastingTx1,
|
|
1273
|
-
};
|
|
1274
|
-
nextState = updateAnchorFamilyState({
|
|
1275
|
-
state: nextState,
|
|
1276
|
-
family,
|
|
1277
|
-
target: operation.targetIdentity,
|
|
1278
|
-
status: "broadcasting",
|
|
1279
|
-
localAnchorIntent: "reserved",
|
|
1280
|
-
currentStep: "tx1",
|
|
1281
|
-
nowUnixMs,
|
|
1282
|
-
tx1: broadcastingTx1,
|
|
1283
|
-
});
|
|
1284
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1285
|
-
ensureSameTipHeight(readContext, (await rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
|
|
1286
|
-
try {
|
|
1287
|
-
await rpc.sendRawTransaction(builtTx1.rawHex);
|
|
1288
|
-
}
|
|
1289
|
-
catch (error) {
|
|
1290
|
-
if (!isAlreadyAcceptedError(error)) {
|
|
1291
|
-
if (isBroadcastUnknownError(error)) {
|
|
1292
|
-
family = {
|
|
1293
|
-
...family,
|
|
1294
|
-
status: "broadcast-unknown",
|
|
1295
|
-
tx1: {
|
|
1296
|
-
...broadcastingTx1,
|
|
1297
|
-
status: "broadcast-unknown",
|
|
1298
|
-
},
|
|
1299
|
-
};
|
|
1300
|
-
nextState = updateAnchorFamilyState({
|
|
1301
|
-
state: nextState,
|
|
1302
|
-
family,
|
|
1303
|
-
target: operation.targetIdentity,
|
|
1304
|
-
status: "broadcast-unknown",
|
|
1305
|
-
localAnchorIntent: "reserved",
|
|
1306
|
-
currentStep: "tx1",
|
|
1307
|
-
nowUnixMs,
|
|
1308
|
-
tx1: family.tx1,
|
|
1309
|
-
});
|
|
1310
|
-
await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1311
|
-
throw new Error("wallet_anchor_tx1_broadcast_unknown");
|
|
1312
|
-
}
|
|
1313
|
-
await unlockTemporaryBuilderLocks(rpc, walletName, builtTx1.temporaryBuilderLockedOutpoints);
|
|
1314
|
-
family = {
|
|
1315
|
-
...family,
|
|
1316
|
-
status: "canceled",
|
|
1317
|
-
tx1: {
|
|
1318
|
-
...broadcastingTx1,
|
|
1319
|
-
status: "canceled",
|
|
1320
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1321
|
-
},
|
|
422
|
+
const reuse = await resolvePendingMutationReuseDecision({
|
|
423
|
+
rpc,
|
|
424
|
+
walletName,
|
|
425
|
+
mutation: reconciled.mutation,
|
|
426
|
+
nextFeeSelection: feeSelection,
|
|
427
|
+
});
|
|
428
|
+
if (reuse.reuseExisting) {
|
|
429
|
+
return {
|
|
430
|
+
domainName: normalizedDomainName,
|
|
431
|
+
txid: reconciled.mutation.attemptedTxid ?? "unknown",
|
|
432
|
+
status: reconciled.resolution,
|
|
433
|
+
reusedExisting: true,
|
|
434
|
+
foundingMessageText: message.text,
|
|
435
|
+
fees: reuse.fees,
|
|
1322
436
|
};
|
|
1323
|
-
nextState = updateAnchorFamilyState({
|
|
1324
|
-
state: nextState,
|
|
1325
|
-
family,
|
|
1326
|
-
target: operation.targetIdentity,
|
|
1327
|
-
status: "canceled",
|
|
1328
|
-
localAnchorIntent: "none",
|
|
1329
|
-
currentStep: "tx1",
|
|
1330
|
-
nowUnixMs,
|
|
1331
|
-
tx1: family.tx1,
|
|
1332
|
-
});
|
|
1333
|
-
await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1334
|
-
throw error;
|
|
1335
437
|
}
|
|
438
|
+
replacementFixedInputs = reuse.replacementFixedInputs;
|
|
1336
439
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
...family,
|
|
1340
|
-
status: "live",
|
|
1341
|
-
currentStep: "tx1",
|
|
1342
|
-
tx1: {
|
|
1343
|
-
...broadcastingTx1,
|
|
1344
|
-
status: "live",
|
|
1345
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1346
|
-
},
|
|
1347
|
-
};
|
|
1348
|
-
nextState = updateAnchorFamilyState({
|
|
1349
|
-
state: nextState,
|
|
1350
|
-
family,
|
|
1351
|
-
target: operation.targetIdentity,
|
|
1352
|
-
status: "live",
|
|
1353
|
-
localAnchorIntent: "tx1-live",
|
|
1354
|
-
currentStep: "tx1",
|
|
1355
|
-
nowUnixMs,
|
|
1356
|
-
tx1: family.tx1,
|
|
1357
|
-
listingCancelCommitted: operation.hadListing,
|
|
1358
|
-
moveOwnershipToTarget: true,
|
|
1359
|
-
});
|
|
1360
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1361
|
-
if (operation.sourceAnchorOutpoint !== null) {
|
|
1362
|
-
await relockAnchorOutpoint(rpc, walletName, {
|
|
1363
|
-
txid: builtTx1.txid,
|
|
1364
|
-
vout: 2,
|
|
1365
|
-
});
|
|
440
|
+
if (reconciled.resolution === "repair-required") {
|
|
441
|
+
throw new Error("wallet_anchor_repair_required");
|
|
1366
442
|
}
|
|
1367
443
|
}
|
|
1368
|
-
|
|
444
|
+
await confirmDirectAnchor(options.prompter, {
|
|
445
|
+
domainName: normalizedDomainName,
|
|
446
|
+
walletAddress: state.funding.address,
|
|
447
|
+
foundingMessageText: message.text,
|
|
448
|
+
});
|
|
449
|
+
let nextState = upsertPendingMutation(workingState, createDraftAnchorMutation({
|
|
450
|
+
state: workingState,
|
|
451
|
+
domainName: normalizedDomainName,
|
|
452
|
+
intentFingerprintHex,
|
|
453
|
+
nowUnixMs,
|
|
454
|
+
feeSelection,
|
|
455
|
+
existing: existingMutation ?? null,
|
|
456
|
+
}));
|
|
457
|
+
nextState = await saveState({
|
|
1369
458
|
state: nextState,
|
|
1370
|
-
family,
|
|
1371
|
-
operation,
|
|
1372
|
-
readContext: operation.readContext,
|
|
1373
459
|
provider,
|
|
460
|
+
nowUnixMs,
|
|
461
|
+
paths,
|
|
462
|
+
});
|
|
463
|
+
const directAnchorPlan = buildDirectAnchorPlan({
|
|
464
|
+
state: nextState,
|
|
465
|
+
allUtxos: await rpc.listUnspent(walletName, 1),
|
|
466
|
+
domainId: chainDomain.domainId,
|
|
467
|
+
foundingMessagePayloadHex: message.payloadHex,
|
|
468
|
+
});
|
|
469
|
+
const built = await buildWalletMutationTransactionWithReserveFallback({
|
|
1374
470
|
rpc,
|
|
1375
471
|
walletName,
|
|
472
|
+
state: nextState,
|
|
473
|
+
plan: {
|
|
474
|
+
...directAnchorPlan,
|
|
475
|
+
fixedInputs: mergeFixedWalletInputs(directAnchorPlan.fixedInputs, replacementFixedInputs),
|
|
476
|
+
},
|
|
477
|
+
validateFundedDraft: validateDirectAnchorDraft,
|
|
478
|
+
finalizeErrorCode: "wallet_anchor_finalize_failed",
|
|
479
|
+
mempoolRejectPrefix: "wallet_anchor_mempool_rejected",
|
|
480
|
+
feeRate: feeSelection.feeRateSatVb,
|
|
481
|
+
});
|
|
482
|
+
const currentMutation = nextState.pendingMutations?.find((mutation) => mutation.intentFingerprintHex === intentFingerprintHex)
|
|
483
|
+
?? createDraftAnchorMutation({
|
|
484
|
+
state: nextState,
|
|
485
|
+
domainName: normalizedDomainName,
|
|
486
|
+
intentFingerprintHex,
|
|
487
|
+
nowUnixMs,
|
|
488
|
+
feeSelection,
|
|
489
|
+
});
|
|
490
|
+
const broadcastingMutation = updateMutationRecord(currentMutation, "broadcasting", nowUnixMs, {
|
|
491
|
+
attemptedTxid: built.txid,
|
|
492
|
+
attemptedWtxid: built.wtxid,
|
|
493
|
+
temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
|
|
494
|
+
});
|
|
495
|
+
nextState = await saveState({
|
|
496
|
+
state: upsertPendingMutation(nextState, broadcastingMutation),
|
|
497
|
+
provider,
|
|
1376
498
|
nowUnixMs,
|
|
1377
499
|
paths,
|
|
1378
|
-
unlockUntilUnixMs: operation.unlockUntilUnixMs,
|
|
1379
500
|
});
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1386
|
-
finally {
|
|
1387
|
-
await readContext.close();
|
|
1388
|
-
await miningPreemption.release();
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
finally {
|
|
1392
|
-
await controlLock.release();
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
export async function clearPendingAnchor(options) {
|
|
1396
|
-
const provider = options.provider ?? createDefaultWalletSecretProvider();
|
|
1397
|
-
const nowUnixMs = options.nowUnixMs ?? Date.now();
|
|
1398
|
-
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
1399
|
-
const controlLock = await acquireFileLock(paths.walletControlLockPath, {
|
|
1400
|
-
purpose: "wallet-anchor-clear",
|
|
1401
|
-
walletRootId: null,
|
|
1402
|
-
});
|
|
1403
|
-
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1404
|
-
try {
|
|
1405
|
-
const miningPreemption = await pauseMiningForWalletMutation({
|
|
1406
|
-
paths,
|
|
1407
|
-
reason: "wallet-anchor-clear",
|
|
1408
|
-
});
|
|
1409
|
-
const readContext = await (options.openReadContext ?? openWalletReadContext)({
|
|
1410
|
-
dataDir: options.dataDir,
|
|
1411
|
-
databasePath: options.databasePath,
|
|
1412
|
-
secretProvider: provider,
|
|
1413
|
-
walletControlLockHeld: true,
|
|
1414
|
-
paths,
|
|
1415
|
-
});
|
|
1416
|
-
try {
|
|
1417
|
-
assertWalletMutationContextReady(readContext, "wallet_anchor_clear");
|
|
1418
|
-
const family = findActiveAnchorFamilyByDomain(readContext.localState.state, normalizedDomainName);
|
|
1419
|
-
const domain = readContext.localState.state.domains.find((entry) => entry.name === normalizedDomainName) ?? null;
|
|
1420
|
-
if (domain === null && family === null) {
|
|
1421
|
-
throw new Error("wallet_anchor_clear_domain_not_found");
|
|
501
|
+
ensureSameTipHeight(readContext, (await rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
|
|
502
|
+
let accepted = false;
|
|
503
|
+
try {
|
|
504
|
+
await rpc.sendRawTransaction(built.rawHex);
|
|
505
|
+
accepted = true;
|
|
1422
506
|
}
|
|
1423
|
-
|
|
1424
|
-
if (
|
|
1425
|
-
|
|
507
|
+
catch (error) {
|
|
508
|
+
if (isAlreadyAcceptedError(error)) {
|
|
509
|
+
accepted = true;
|
|
1426
510
|
}
|
|
1427
|
-
if (
|
|
1428
|
-
|
|
511
|
+
else if (isBroadcastUnknownError(error)) {
|
|
512
|
+
const unknownMutation = updateMutationRecord(broadcastingMutation, "broadcast-unknown", nowUnixMs, {
|
|
513
|
+
attemptedTxid: built.txid,
|
|
514
|
+
attemptedWtxid: built.wtxid,
|
|
515
|
+
temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
|
|
516
|
+
});
|
|
517
|
+
await saveState({
|
|
518
|
+
state: upsertPendingMutation(nextState, unknownMutation),
|
|
519
|
+
provider,
|
|
520
|
+
nowUnixMs,
|
|
521
|
+
paths,
|
|
522
|
+
});
|
|
523
|
+
throw new Error("wallet_anchor_broadcast_unknown");
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
|
|
527
|
+
const canceledMutation = updateMutationRecord(broadcastingMutation, "canceled", nowUnixMs, {
|
|
528
|
+
attemptedTxid: built.txid,
|
|
529
|
+
attemptedWtxid: built.wtxid,
|
|
530
|
+
temporaryBuilderLockedOutpoints: [],
|
|
531
|
+
});
|
|
532
|
+
await saveState({
|
|
533
|
+
state: upsertPendingMutation(nextState, canceledMutation),
|
|
534
|
+
provider,
|
|
535
|
+
nowUnixMs,
|
|
536
|
+
paths,
|
|
537
|
+
});
|
|
538
|
+
throw error;
|
|
1429
539
|
}
|
|
1430
|
-
return {
|
|
1431
|
-
domainName: normalizedDomainName,
|
|
1432
|
-
cleared: false,
|
|
1433
|
-
previousFamilyStatus: null,
|
|
1434
|
-
previousFamilyStep: null,
|
|
1435
|
-
releasedDedicatedIndex: null,
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
1438
|
-
if (family.type !== "anchor") {
|
|
1439
|
-
throw new Error("wallet_anchor_clear_inconsistent_state");
|
|
1440
|
-
}
|
|
1441
|
-
if (family.status !== "draft" || family.currentStep !== "reserved") {
|
|
1442
|
-
throw new Error(`wallet_anchor_clear_not_clearable_${family.status}`);
|
|
1443
540
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|| family.tx1?.attemptedTxid !== null
|
|
1447
|
-
|| family.tx2?.attemptedTxid !== null
|
|
1448
|
-
|| (domain !== null
|
|
1449
|
-
&& (domain.localAnchorIntent !== "reserved"
|
|
1450
|
-
|| domain.dedicatedIndex === null
|
|
1451
|
-
|| domain.dedicatedIndex !== reservedDedicatedIndex))) {
|
|
1452
|
-
throw new Error("wallet_anchor_clear_inconsistent_state");
|
|
541
|
+
if (!accepted) {
|
|
542
|
+
throw new Error("wallet_anchor_broadcast_failed");
|
|
1453
543
|
}
|
|
1454
|
-
await
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
544
|
+
await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
|
|
545
|
+
const finalStatus = anchorConfirmedOnSnapshot({
|
|
546
|
+
snapshot: readContext.snapshot,
|
|
547
|
+
state: nextState,
|
|
1458
548
|
domainName: normalizedDomainName,
|
|
1459
|
-
|
|
549
|
+
}) ? "confirmed" : "live";
|
|
550
|
+
const finalMutation = updateMutationRecord(broadcastingMutation, finalStatus, nowUnixMs, {
|
|
551
|
+
attemptedTxid: built.txid,
|
|
552
|
+
attemptedWtxid: built.wtxid,
|
|
553
|
+
temporaryBuilderLockedOutpoints: [],
|
|
1460
554
|
});
|
|
1461
|
-
|
|
1462
|
-
state:
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
555
|
+
nextState = upsertAnchoredDomainRecord({
|
|
556
|
+
state: upsertPendingMutation(nextState, finalMutation),
|
|
557
|
+
domainName: normalizedDomainName,
|
|
558
|
+
domainId: chainDomain.domainId,
|
|
559
|
+
foundingMessageText: message.text,
|
|
560
|
+
});
|
|
561
|
+
nextState = await saveState({
|
|
562
|
+
state: nextState,
|
|
1467
563
|
provider,
|
|
1468
|
-
unlockUntilUnixMs: readContext.localState.unlockUntilUnixMs,
|
|
1469
564
|
nowUnixMs,
|
|
1470
565
|
paths,
|
|
1471
566
|
});
|
|
1472
567
|
return {
|
|
1473
568
|
domainName: normalizedDomainName,
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
569
|
+
txid: built.txid,
|
|
570
|
+
status: finalStatus,
|
|
571
|
+
reusedExisting: false,
|
|
572
|
+
foundingMessageText: message.text,
|
|
573
|
+
fees: createBuiltWalletMutationFeeSummary({
|
|
574
|
+
selection: feeSelection,
|
|
575
|
+
built,
|
|
576
|
+
}),
|
|
1478
577
|
};
|
|
1479
578
|
}
|
|
1480
579
|
finally {
|