@cogcoin/client 0.5.14 → 1.0.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.
- 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 +16 -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 +38 -3
- package/dist/cli/commands/service-runtime.js +0 -2
- package/dist/cli/commands/status.js +8 -2
- package/dist/cli/commands/sync.js +51 -4
- 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 +4 -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/types.d.ts +8 -17
- package/dist/cli/types.js +0 -2
- package/dist/cli/wallet-format.d.ts +1 -0
- package/dist/cli/wallet-format.js +205 -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 -11
- package/dist/wallet/coin-control.js +100 -357
- 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 +3 -4
- package/dist/wallet/mining/index.js +1 -2
- package/dist/wallet/mining/runner.d.ts +179 -6
- package/dist/wallet/mining/runner.js +891 -501
- 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 -1240
- 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 +74 -10
- package/dist/wallet/tx/common.js +315 -138
- 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 +84 -914
- 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,892 +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 foundingPayload = options.operation.foundingMessagePayloadHex === null
|
|
555
|
-
? undefined
|
|
556
|
-
: Buffer.from(options.operation.foundingMessagePayloadHex, "hex");
|
|
557
|
-
const opReturnData = serializeDomainAnchor(options.operation.chainDomain.domainId, foundingPayload).opReturnData;
|
|
558
|
-
return {
|
|
559
|
-
sender: {
|
|
560
|
-
localIndex: options.operation.targetIdentity.localIndex,
|
|
561
|
-
scriptPubKeyHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
562
|
-
address: options.operation.targetIdentity.address,
|
|
563
|
-
},
|
|
564
|
-
changeAddress: options.state.funding.address,
|
|
565
|
-
fixedInputs: [{ txid: provisional.txid, vout: provisional.vout }],
|
|
566
|
-
outputs: [
|
|
567
|
-
{ data: Buffer.from(opReturnData).toString("hex") },
|
|
568
|
-
{ [options.operation.targetIdentity.address]: satsToBtcNumber(BigInt(options.state.anchorValueSats)) },
|
|
569
|
-
],
|
|
570
|
-
changePosition: 2,
|
|
571
|
-
expectedOpReturnScriptHex: encodeOpReturnScript(opReturnData),
|
|
572
|
-
expectedProvisionalAnchorScriptHex: options.operation.targetIdentity.scriptPubKeyHex,
|
|
573
|
-
expectedProvisionalAnchorValueSats: BigInt(options.state.anchorValueSats),
|
|
574
|
-
expectedReplacementAnchorScriptHex: null,
|
|
575
|
-
expectedReplacementAnchorValueSats: null,
|
|
576
|
-
allowedFundingScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
|
|
577
|
-
eligibleFundingOutpointKeys: new Set(fundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout }))),
|
|
578
|
-
requiredSenderOutpoint: null,
|
|
579
|
-
requiredProvisionalOutpoint: {
|
|
580
|
-
txid: provisional.txid,
|
|
581
|
-
vout: provisional.vout,
|
|
582
|
-
},
|
|
583
|
-
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,
|
|
584
231
|
};
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
591
|
-
}
|
|
592
|
-
assertFixedInputPrefixMatches(inputs, plan.fixedInputs, `${plan.errorPrefix}_sender_input_mismatch`);
|
|
593
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(inputs[0]);
|
|
594
|
-
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex) {
|
|
595
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
596
|
-
}
|
|
597
|
-
if (plan.requiredSenderOutpoint !== null) {
|
|
598
|
-
if (!inputMatchesOutpoint(inputs[0], plan.requiredSenderOutpoint)) {
|
|
599
|
-
throw new Error(`${plan.errorPrefix}_sender_input_mismatch`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
assertFundingInputsAfterFixedPrefix({
|
|
603
|
-
inputs,
|
|
604
|
-
fixedInputs: plan.fixedInputs,
|
|
605
|
-
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
606
|
-
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
607
|
-
errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
|
|
232
|
+
await saveWalletStatePreservingUnlock({
|
|
233
|
+
state: nextState,
|
|
234
|
+
provider: options.provider,
|
|
235
|
+
nowUnixMs: options.nowUnixMs,
|
|
236
|
+
paths: options.paths,
|
|
608
237
|
});
|
|
609
|
-
|
|
610
|
-
throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
|
|
611
|
-
}
|
|
612
|
-
if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
|
|
613
|
-
throw new Error(`${plan.errorPrefix}_provisional_anchor_output_mismatch`);
|
|
614
|
-
}
|
|
615
|
-
if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
|
|
616
|
-
throw new Error(`${plan.errorPrefix}_provisional_anchor_value_mismatch`);
|
|
617
|
-
}
|
|
618
|
-
const expectedWithoutChange = plan.expectedReplacementAnchorScriptHex === null ? 2 : 3;
|
|
619
|
-
if (plan.expectedReplacementAnchorScriptHex !== null) {
|
|
620
|
-
if (outputs[2]?.scriptPubKey?.hex !== plan.expectedReplacementAnchorScriptHex) {
|
|
621
|
-
throw new Error(`${plan.errorPrefix}_replacement_anchor_output_mismatch`);
|
|
622
|
-
}
|
|
623
|
-
if (valueToSats(outputs[2]?.value ?? 0) !== (plan.expectedReplacementAnchorValueSats ?? 0n)) {
|
|
624
|
-
throw new Error(`${plan.errorPrefix}_replacement_anchor_value_mismatch`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
if (funded.changepos === -1) {
|
|
628
|
-
if (outputs.length !== expectedWithoutChange) {
|
|
629
|
-
throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
|
|
630
|
-
}
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
|
|
634
|
-
throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
|
|
635
|
-
}
|
|
636
|
-
if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
|
|
637
|
-
throw new Error(`${plan.errorPrefix}_change_output_mismatch`);
|
|
638
|
-
}
|
|
238
|
+
return nextState;
|
|
639
239
|
}
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const firstInputScriptPubKeyHex = getDecodedInputScriptPubKeyHex(inputs[0]);
|
|
648
|
-
if (firstInputScriptPubKeyHex !== plan.sender.scriptPubKeyHex
|
|
649
|
-
|| !inputMatchesOutpoint(inputs[0], plan.requiredProvisionalOutpoint)) {
|
|
650
|
-
throw new Error(`${plan.errorPrefix}_provisional_input_mismatch`);
|
|
651
|
-
}
|
|
652
|
-
assertFundingInputsAfterFixedPrefix({
|
|
653
|
-
inputs,
|
|
654
|
-
fixedInputs: plan.fixedInputs,
|
|
655
|
-
allowedFundingScriptPubKeyHex: plan.allowedFundingScriptPubKeyHex,
|
|
656
|
-
eligibleFundingOutpointKeys: plan.eligibleFundingOutpointKeys,
|
|
657
|
-
errorCode: `${plan.errorPrefix}_unexpected_funding_input`,
|
|
658
|
-
});
|
|
659
|
-
if (outputs[0]?.scriptPubKey?.hex !== plan.expectedOpReturnScriptHex) {
|
|
660
|
-
throw new Error(`${plan.errorPrefix}_opreturn_mismatch`);
|
|
661
|
-
}
|
|
662
|
-
if (outputs[1]?.scriptPubKey?.hex !== plan.expectedProvisionalAnchorScriptHex) {
|
|
663
|
-
throw new Error(`${plan.errorPrefix}_canonical_anchor_output_mismatch`);
|
|
664
|
-
}
|
|
665
|
-
if (valueToSats(outputs[1]?.value ?? 0) !== plan.expectedProvisionalAnchorValueSats) {
|
|
666
|
-
throw new Error(`${plan.errorPrefix}_canonical_anchor_value_mismatch`);
|
|
667
|
-
}
|
|
668
|
-
const expectedWithoutChange = 2;
|
|
669
|
-
if (funded.changepos === -1) {
|
|
670
|
-
if (outputs.length !== expectedWithoutChange) {
|
|
671
|
-
throw new Error(`${plan.errorPrefix}_unexpected_output_count`);
|
|
672
|
-
}
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
if (funded.changepos !== plan.changePosition || outputs.length !== expectedWithoutChange + 1) {
|
|
676
|
-
throw new Error(`${plan.errorPrefix}_change_position_mismatch`);
|
|
677
|
-
}
|
|
678
|
-
if (outputs[funded.changepos]?.scriptPubKey?.hex !== plan.allowedFundingScriptPubKeyHex) {
|
|
679
|
-
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
|
+
};
|
|
680
247
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
return buildWalletMutationTransactionWithReserveFallback({
|
|
684
|
-
rpc: options.rpc,
|
|
685
|
-
walletName: options.walletName,
|
|
686
|
-
state: options.state,
|
|
687
|
-
plan: options.plan,
|
|
688
|
-
validateFundedDraft: validateTx1Draft,
|
|
689
|
-
finalizeErrorCode: "wallet_anchor_tx1_finalize_failed",
|
|
690
|
-
mempoolRejectPrefix: "wallet_anchor_tx1_mempool_rejected",
|
|
691
|
-
reserveCandidates: options.state.proactiveReserveOutpoints,
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
async function buildTx2(options) {
|
|
695
|
-
return buildWalletMutationTransactionWithReserveFallback({
|
|
696
|
-
rpc: options.rpc,
|
|
697
|
-
walletName: options.walletName,
|
|
248
|
+
if (options.context.snapshot !== null && anchorConfirmedOnSnapshot({
|
|
249
|
+
snapshot: options.context.snapshot,
|
|
698
250
|
state: options.state,
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
? null
|
|
715
|
-
: lookupDomain(options.snapshot.state, options.family.domainName);
|
|
716
|
-
if (chainDomain === null) {
|
|
717
|
-
return "live";
|
|
718
|
-
}
|
|
719
|
-
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
720
|
-
return chainDomain.anchored && ownerHex === options.target.scriptPubKeyHex
|
|
721
|
-
? "confirmed"
|
|
722
|
-
: "live";
|
|
723
|
-
}
|
|
724
|
-
async function reconcileAnchorFamily(options) {
|
|
725
|
-
const chainDomain = lookupDomain(options.operation.readContext.snapshot.state, options.operation.chainDomain.name);
|
|
726
|
-
const targetScript = options.operation.targetIdentity.scriptPubKeyHex;
|
|
727
|
-
if (chainDomain !== null) {
|
|
728
|
-
const ownerHex = Buffer.from(chainDomain.ownerScriptPubKey).toString("hex");
|
|
729
|
-
if (chainDomain.anchored && ownerHex === targetScript) {
|
|
730
|
-
const nextState = updateAnchorFamilyState({
|
|
731
|
-
state: options.state,
|
|
732
|
-
family: options.family,
|
|
733
|
-
target: options.operation.targetIdentity,
|
|
734
|
-
status: "confirmed",
|
|
735
|
-
localAnchorIntent: "none",
|
|
736
|
-
currentStep: "tx2",
|
|
737
|
-
nowUnixMs: options.nowUnixMs,
|
|
738
|
-
tx1: options.family.tx1 == null ? undefined : { ...options.family.tx1, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
|
|
739
|
-
tx2: options.family.tx2 == null ? undefined : { ...options.family.tx2, status: "confirmed", temporaryBuilderLockedOutpoints: [] },
|
|
740
|
-
moveOwnershipToTarget: true,
|
|
741
|
-
canonicalChainStatus: "anchored",
|
|
742
|
-
currentCanonicalAnchorOutpoint: options.family.tx2?.attemptedTxid == null
|
|
743
|
-
? options.state.domains.find((domain) => domain.name === options.family.domainName)?.currentCanonicalAnchorOutpoint ?? null
|
|
744
|
-
: {
|
|
745
|
-
txid: options.family.tx2.attemptedTxid,
|
|
746
|
-
vout: 1,
|
|
747
|
-
valueSats: options.state.anchorValueSats,
|
|
748
|
-
},
|
|
749
|
-
});
|
|
750
|
-
await saveWalletStatePreservingUnlock({
|
|
751
|
-
state: {
|
|
752
|
-
...nextState,
|
|
753
|
-
stateRevision: nextState.stateRevision + 1,
|
|
754
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
755
|
-
},
|
|
756
|
-
provider: options.provider,
|
|
757
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
758
|
-
nowUnixMs: options.nowUnixMs,
|
|
759
|
-
paths: options.paths,
|
|
760
|
-
});
|
|
761
|
-
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({
|
|
762
266
|
state: nextState,
|
|
763
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
764
|
-
...options.family,
|
|
765
|
-
status: "confirmed",
|
|
766
|
-
},
|
|
767
|
-
resolution: "confirmed",
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
if (ownerHex === targetScript && !chainDomain.anchored) {
|
|
771
|
-
const nextState = updateAnchorFamilyState({
|
|
772
|
-
state: options.state,
|
|
773
|
-
family: options.family,
|
|
774
|
-
target: options.operation.targetIdentity,
|
|
775
|
-
status: "repair-required",
|
|
776
|
-
localAnchorIntent: "repair-required",
|
|
777
|
-
currentStep: "tx2",
|
|
778
|
-
nowUnixMs: options.nowUnixMs,
|
|
779
|
-
listingCancelCommitted: true,
|
|
780
|
-
moveOwnershipToTarget: true,
|
|
781
|
-
});
|
|
782
|
-
await saveWalletStatePreservingUnlock({
|
|
783
|
-
state: {
|
|
784
|
-
...nextState,
|
|
785
|
-
stateRevision: nextState.stateRevision + 1,
|
|
786
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
787
|
-
},
|
|
788
267
|
provider: options.provider,
|
|
789
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
790
268
|
nowUnixMs: options.nowUnixMs,
|
|
791
269
|
paths: options.paths,
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
796
|
-
...options.family,
|
|
797
|
-
status: "repair-required",
|
|
798
|
-
},
|
|
799
|
-
resolution: "repair-required",
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
const mempool = await options.rpc.getRawMempool().catch(() => []);
|
|
804
|
-
if (options.family.tx2?.attemptedTxid != null && mempool.includes(options.family.tx2.attemptedTxid)) {
|
|
805
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, options.family.tx2.temporaryBuilderLockedOutpoints);
|
|
806
|
-
const nextState = updateAnchorFamilyState({
|
|
807
|
-
state: options.state,
|
|
808
|
-
family: options.family,
|
|
809
|
-
target: options.operation.targetIdentity,
|
|
810
|
-
status: "live",
|
|
811
|
-
localAnchorIntent: "tx2-live",
|
|
812
|
-
currentStep: "tx2",
|
|
813
|
-
nowUnixMs: options.nowUnixMs,
|
|
814
|
-
tx2: {
|
|
815
|
-
...options.family.tx2,
|
|
816
|
-
status: "live",
|
|
817
|
-
temporaryBuilderLockedOutpoints: [],
|
|
818
|
-
},
|
|
819
|
-
listingCancelCommitted: true,
|
|
820
|
-
moveOwnershipToTarget: true,
|
|
821
|
-
currentCanonicalAnchorOutpoint: {
|
|
822
|
-
txid: options.family.tx2.attemptedTxid,
|
|
823
|
-
vout: 1,
|
|
824
|
-
valueSats: options.state.anchorValueSats,
|
|
825
|
-
},
|
|
826
|
-
});
|
|
827
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
828
|
-
txid: options.family.tx2.attemptedTxid,
|
|
829
|
-
vout: 1,
|
|
830
|
-
});
|
|
831
|
-
await saveWalletStatePreservingUnlock({
|
|
832
|
-
state: {
|
|
833
|
-
...nextState,
|
|
834
|
-
stateRevision: nextState.stateRevision + 1,
|
|
835
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
836
|
-
},
|
|
837
|
-
provider: options.provider,
|
|
838
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
839
|
-
nowUnixMs: options.nowUnixMs,
|
|
840
|
-
paths: options.paths,
|
|
841
|
-
});
|
|
842
|
-
return {
|
|
843
|
-
state: nextState,
|
|
844
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
845
|
-
...options.family,
|
|
846
|
-
status: "live",
|
|
847
|
-
},
|
|
848
|
-
resolution: "live",
|
|
270
|
+
}),
|
|
271
|
+
mutation: confirmedMutation,
|
|
272
|
+
resolution: "confirmed",
|
|
849
273
|
};
|
|
850
274
|
}
|
|
851
|
-
if (options.
|
|
852
|
-
await
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
target: options.operation.targetIdentity,
|
|
857
|
-
status: "live",
|
|
858
|
-
localAnchorIntent: "tx1-live",
|
|
859
|
-
currentStep: "tx1",
|
|
860
|
-
nowUnixMs: options.nowUnixMs,
|
|
861
|
-
tx1: {
|
|
862
|
-
...options.family.tx1,
|
|
863
|
-
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, {
|
|
864
280
|
temporaryBuilderLockedOutpoints: [],
|
|
865
|
-
},
|
|
866
|
-
listingCancelCommitted: options.operation.hadListing,
|
|
867
|
-
moveOwnershipToTarget: true,
|
|
868
|
-
});
|
|
869
|
-
if (options.operation.sourceAnchorOutpoint !== null) {
|
|
870
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
871
|
-
txid: options.family.tx1.attemptedTxid,
|
|
872
|
-
vout: 2,
|
|
873
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
|
+
};
|
|
874
303
|
}
|
|
875
|
-
await saveWalletStatePreservingUnlock({
|
|
876
|
-
state: {
|
|
877
|
-
...nextState,
|
|
878
|
-
stateRevision: nextState.stateRevision + 1,
|
|
879
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
880
|
-
},
|
|
881
|
-
provider: options.provider,
|
|
882
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
883
|
-
nowUnixMs: options.nowUnixMs,
|
|
884
|
-
paths: options.paths,
|
|
885
|
-
});
|
|
886
|
-
return {
|
|
887
|
-
state: nextState,
|
|
888
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
889
|
-
...options.family,
|
|
890
|
-
status: "live",
|
|
891
|
-
},
|
|
892
|
-
resolution: "ready-for-tx2",
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
if (options.family.currentStep === "tx2" || options.family.tx2?.attemptedTxid != null) {
|
|
896
|
-
const nextState = updateAnchorFamilyState({
|
|
897
|
-
state: options.state,
|
|
898
|
-
family: options.family,
|
|
899
|
-
target: options.operation.targetIdentity,
|
|
900
|
-
status: "repair-required",
|
|
901
|
-
localAnchorIntent: "repair-required",
|
|
902
|
-
currentStep: "tx2",
|
|
903
|
-
nowUnixMs: options.nowUnixMs,
|
|
904
|
-
listingCancelCommitted: true,
|
|
905
|
-
moveOwnershipToTarget: true,
|
|
906
|
-
});
|
|
907
|
-
await saveWalletStatePreservingUnlock({
|
|
908
|
-
state: {
|
|
909
|
-
...nextState,
|
|
910
|
-
stateRevision: nextState.stateRevision + 1,
|
|
911
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
912
|
-
},
|
|
913
|
-
provider: options.provider,
|
|
914
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
915
|
-
nowUnixMs: options.nowUnixMs,
|
|
916
|
-
paths: options.paths,
|
|
917
|
-
});
|
|
918
|
-
return {
|
|
919
|
-
state: nextState,
|
|
920
|
-
family: findAnchorFamilyById(nextState, options.family.familyId) ?? {
|
|
921
|
-
...options.family,
|
|
922
|
-
status: "repair-required",
|
|
923
|
-
},
|
|
924
|
-
resolution: "repair-required",
|
|
925
|
-
};
|
|
926
304
|
}
|
|
927
|
-
if (
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
currentStep: options.family.currentStep,
|
|
935
|
-
nowUnixMs: options.nowUnixMs,
|
|
936
|
-
tx1: options.family.tx1 == null ? undefined : {
|
|
937
|
-
...options.family.tx1,
|
|
938
|
-
status: "canceled",
|
|
939
|
-
temporaryBuilderLockedOutpoints: [],
|
|
940
|
-
},
|
|
941
|
-
tx2: options.family.tx2 == null ? undefined : {
|
|
942
|
-
...options.family.tx2,
|
|
943
|
-
status: "canceled",
|
|
944
|
-
temporaryBuilderLockedOutpoints: [],
|
|
945
|
-
},
|
|
946
|
-
});
|
|
947
|
-
await saveWalletStatePreservingUnlock({
|
|
948
|
-
state: {
|
|
949
|
-
...nextState,
|
|
950
|
-
stateRevision: nextState.stateRevision + 1,
|
|
951
|
-
lastWrittenAtUnixMs: options.nowUnixMs,
|
|
952
|
-
},
|
|
953
|
-
provider: options.provider,
|
|
954
|
-
unlockUntilUnixMs: options.unlockUntilUnixMs,
|
|
955
|
-
nowUnixMs: options.nowUnixMs,
|
|
956
|
-
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: [],
|
|
957
312
|
});
|
|
313
|
+
const nextState = upsertPendingMutation(options.state, canceledMutation);
|
|
958
314
|
return {
|
|
959
|
-
state:
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
315
|
+
state: await saveState({
|
|
316
|
+
state: nextState,
|
|
317
|
+
provider: options.provider,
|
|
318
|
+
nowUnixMs: options.nowUnixMs,
|
|
319
|
+
paths: options.paths,
|
|
320
|
+
}),
|
|
321
|
+
mutation: canceledMutation,
|
|
964
322
|
resolution: "not-seen",
|
|
965
323
|
};
|
|
966
324
|
}
|
|
967
325
|
return {
|
|
968
326
|
state: options.state,
|
|
969
|
-
|
|
327
|
+
mutation: options.mutation,
|
|
970
328
|
resolution: "continue",
|
|
971
329
|
};
|
|
972
330
|
}
|
|
973
|
-
function createBroadcastingTxRecord(built) {
|
|
974
|
-
return {
|
|
975
|
-
status: "broadcasting",
|
|
976
|
-
attemptedTxid: built.txid,
|
|
977
|
-
attemptedWtxid: built.wtxid,
|
|
978
|
-
temporaryBuilderLockedOutpoints: built.temporaryBuilderLockedOutpoints,
|
|
979
|
-
rawHex: built.rawHex,
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
async function saveState(state, provider, unlockUntilUnixMs, nowUnixMs, paths) {
|
|
983
|
-
const nextState = {
|
|
984
|
-
...state,
|
|
985
|
-
stateRevision: state.stateRevision + 1,
|
|
986
|
-
lastWrittenAtUnixMs: nowUnixMs,
|
|
987
|
-
};
|
|
988
|
-
await saveWalletStatePreservingUnlock({
|
|
989
|
-
state: nextState,
|
|
990
|
-
provider,
|
|
991
|
-
unlockUntilUnixMs,
|
|
992
|
-
nowUnixMs,
|
|
993
|
-
paths,
|
|
994
|
-
});
|
|
995
|
-
return nextState;
|
|
996
|
-
}
|
|
997
|
-
async function submitTx2(options) {
|
|
998
|
-
let nextState = options.state;
|
|
999
|
-
let family = options.family;
|
|
1000
|
-
const tx2Plan = buildTx2Plan({
|
|
1001
|
-
state: nextState,
|
|
1002
|
-
allUtxos: await options.rpc.listUnspent(options.walletName, 0),
|
|
1003
|
-
operation: options.operation,
|
|
1004
|
-
family,
|
|
1005
|
-
});
|
|
1006
|
-
const builtTx2 = await buildTx2({
|
|
1007
|
-
rpc: options.rpc,
|
|
1008
|
-
walletName: options.walletName,
|
|
1009
|
-
state: nextState,
|
|
1010
|
-
plan: tx2Plan,
|
|
1011
|
-
});
|
|
1012
|
-
const broadcastingTx2 = createBroadcastingTxRecord(builtTx2);
|
|
1013
|
-
family = {
|
|
1014
|
-
...family,
|
|
1015
|
-
status: "broadcasting",
|
|
1016
|
-
currentStep: "tx2",
|
|
1017
|
-
tx2: broadcastingTx2,
|
|
1018
|
-
};
|
|
1019
|
-
nextState = updateAnchorFamilyState({
|
|
1020
|
-
state: nextState,
|
|
1021
|
-
family,
|
|
1022
|
-
target: options.operation.targetIdentity,
|
|
1023
|
-
status: "broadcasting",
|
|
1024
|
-
localAnchorIntent: "tx1-live",
|
|
1025
|
-
currentStep: "tx2",
|
|
1026
|
-
nowUnixMs: options.nowUnixMs,
|
|
1027
|
-
tx2: broadcastingTx2,
|
|
1028
|
-
listingCancelCommitted: true,
|
|
1029
|
-
moveOwnershipToTarget: true,
|
|
1030
|
-
});
|
|
1031
|
-
nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1032
|
-
ensureSameTipHeight(options.readContext, (await options.rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
|
|
1033
|
-
try {
|
|
1034
|
-
await options.rpc.sendRawTransaction(builtTx2.rawHex);
|
|
1035
|
-
}
|
|
1036
|
-
catch (error) {
|
|
1037
|
-
if (!isAlreadyAcceptedError(error)) {
|
|
1038
|
-
if (isBroadcastUnknownError(error)) {
|
|
1039
|
-
family = {
|
|
1040
|
-
...family,
|
|
1041
|
-
status: "broadcast-unknown",
|
|
1042
|
-
tx2: {
|
|
1043
|
-
...broadcastingTx2,
|
|
1044
|
-
status: "broadcast-unknown",
|
|
1045
|
-
},
|
|
1046
|
-
};
|
|
1047
|
-
nextState = updateAnchorFamilyState({
|
|
1048
|
-
state: nextState,
|
|
1049
|
-
family,
|
|
1050
|
-
target: options.operation.targetIdentity,
|
|
1051
|
-
status: "broadcast-unknown",
|
|
1052
|
-
localAnchorIntent: "tx1-live",
|
|
1053
|
-
currentStep: "tx2",
|
|
1054
|
-
nowUnixMs: options.nowUnixMs,
|
|
1055
|
-
tx2: family.tx2,
|
|
1056
|
-
listingCancelCommitted: true,
|
|
1057
|
-
moveOwnershipToTarget: true,
|
|
1058
|
-
});
|
|
1059
|
-
await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1060
|
-
throw new Error("wallet_anchor_tx2_broadcast_unknown");
|
|
1061
|
-
}
|
|
1062
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
|
|
1063
|
-
family = {
|
|
1064
|
-
...family,
|
|
1065
|
-
status: "repair-required",
|
|
1066
|
-
tx2: {
|
|
1067
|
-
...broadcastingTx2,
|
|
1068
|
-
status: "repair-required",
|
|
1069
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1070
|
-
},
|
|
1071
|
-
};
|
|
1072
|
-
nextState = updateAnchorFamilyState({
|
|
1073
|
-
state: nextState,
|
|
1074
|
-
family,
|
|
1075
|
-
target: options.operation.targetIdentity,
|
|
1076
|
-
status: "repair-required",
|
|
1077
|
-
localAnchorIntent: "repair-required",
|
|
1078
|
-
currentStep: "tx2",
|
|
1079
|
-
nowUnixMs: options.nowUnixMs,
|
|
1080
|
-
tx2: family.tx2,
|
|
1081
|
-
listingCancelCommitted: true,
|
|
1082
|
-
moveOwnershipToTarget: true,
|
|
1083
|
-
});
|
|
1084
|
-
await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1085
|
-
throw error;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
await unlockTemporaryBuilderLocks(options.rpc, options.walletName, builtTx2.temporaryBuilderLockedOutpoints);
|
|
1089
|
-
const finalStatus = resolveAcceptedFamilyStatus({
|
|
1090
|
-
snapshot: options.readContext.snapshot,
|
|
1091
|
-
family,
|
|
1092
|
-
target: options.operation.targetIdentity,
|
|
1093
|
-
});
|
|
1094
|
-
family = {
|
|
1095
|
-
...family,
|
|
1096
|
-
status: finalStatus,
|
|
1097
|
-
currentStep: "tx2",
|
|
1098
|
-
tx2: {
|
|
1099
|
-
...broadcastingTx2,
|
|
1100
|
-
status: finalStatus,
|
|
1101
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1102
|
-
},
|
|
1103
|
-
};
|
|
1104
|
-
nextState = updateAnchorFamilyState({
|
|
1105
|
-
state: nextState,
|
|
1106
|
-
family,
|
|
1107
|
-
target: options.operation.targetIdentity,
|
|
1108
|
-
status: finalStatus,
|
|
1109
|
-
localAnchorIntent: finalStatus === "confirmed" ? "none" : "tx2-live",
|
|
1110
|
-
currentStep: "tx2",
|
|
1111
|
-
nowUnixMs: options.nowUnixMs,
|
|
1112
|
-
tx2: family.tx2,
|
|
1113
|
-
listingCancelCommitted: true,
|
|
1114
|
-
moveOwnershipToTarget: true,
|
|
1115
|
-
canonicalChainStatus: finalStatus === "confirmed" ? "anchored" : undefined,
|
|
1116
|
-
currentCanonicalAnchorOutpoint: {
|
|
1117
|
-
txid: builtTx2.txid,
|
|
1118
|
-
vout: 1,
|
|
1119
|
-
valueSats: nextState.anchorValueSats,
|
|
1120
|
-
},
|
|
1121
|
-
});
|
|
1122
|
-
nextState = await saveState(nextState, options.provider, options.unlockUntilUnixMs, options.nowUnixMs, options.paths);
|
|
1123
|
-
await relockAnchorOutpoint(options.rpc, options.walletName, {
|
|
1124
|
-
txid: builtTx2.txid,
|
|
1125
|
-
vout: 1,
|
|
1126
|
-
});
|
|
1127
|
-
return {
|
|
1128
|
-
domainName: options.operation.chainDomain.name,
|
|
1129
|
-
txid: builtTx2.txid,
|
|
1130
|
-
tx1Txid: family.tx1?.attemptedTxid ?? "unknown",
|
|
1131
|
-
tx2Txid: builtTx2.txid,
|
|
1132
|
-
dedicatedIndex: options.operation.targetIdentity.localIndex,
|
|
1133
|
-
status: finalStatus,
|
|
1134
|
-
reusedExisting: false,
|
|
1135
|
-
foundingMessageText: options.operation.foundingMessageText,
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
331
|
function ensureSameTipHeight(context, bestHeight, errorCode) {
|
|
1139
332
|
if (context.snapshot?.tip?.height !== bestHeight) {
|
|
1140
333
|
throw new Error(errorCode);
|
|
@@ -1147,21 +340,16 @@ export async function anchorDomain(options) {
|
|
|
1147
340
|
const provider = options.provider ?? createDefaultWalletSecretProvider();
|
|
1148
341
|
const nowUnixMs = options.nowUnixMs ?? Date.now();
|
|
1149
342
|
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
343
|
+
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1150
344
|
const controlLock = await acquireFileLock(paths.walletControlLockPath, {
|
|
1151
345
|
purpose: "wallet-anchor",
|
|
1152
346
|
walletRootId: null,
|
|
1153
347
|
});
|
|
1154
|
-
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1155
348
|
try {
|
|
1156
349
|
const miningPreemption = await pauseMiningForWalletMutation({
|
|
1157
350
|
paths,
|
|
1158
351
|
reason: "wallet-anchor",
|
|
1159
352
|
});
|
|
1160
|
-
const message = await resolveFoundingMessage({
|
|
1161
|
-
foundingMessageText: options.foundingMessageText,
|
|
1162
|
-
promptForFoundingMessageWhenMissing: options.promptForFoundingMessageWhenMissing,
|
|
1163
|
-
prompter: options.prompter,
|
|
1164
|
-
});
|
|
1165
353
|
const readContext = await (options.openReadContext ?? openWalletReadContext)({
|
|
1166
354
|
dataDir: options.dataDir,
|
|
1167
355
|
databasePath: options.databasePath,
|
|
@@ -1170,301 +358,222 @@ export async function anchorDomain(options) {
|
|
|
1170
358
|
paths,
|
|
1171
359
|
});
|
|
1172
360
|
try {
|
|
1173
|
-
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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");
|
|
1179
382
|
}
|
|
1180
|
-
if (
|
|
1181
|
-
throw new Error("
|
|
383
|
+
if (state.funding.address.trim() === "") {
|
|
384
|
+
throw new Error("wallet_anchor_owner_identity_not_supported");
|
|
1182
385
|
}
|
|
386
|
+
const intentFingerprintHex = createIntentFingerprint([
|
|
387
|
+
"anchor",
|
|
388
|
+
state.walletRootId,
|
|
389
|
+
normalizedDomainName,
|
|
390
|
+
state.funding.scriptPubKeyHex,
|
|
391
|
+
message.payloadHex ?? "",
|
|
392
|
+
]);
|
|
1183
393
|
const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
|
|
1184
394
|
dataDir: options.dataDir,
|
|
1185
395
|
chain: "main",
|
|
1186
396
|
startHeight: 0,
|
|
1187
|
-
walletRootId:
|
|
397
|
+
walletRootId: state.walletRootId,
|
|
1188
398
|
});
|
|
1189
399
|
const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
|
|
1190
|
-
const walletName =
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
targetIdentity: existingTargetIdentity,
|
|
1203
|
-
},
|
|
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,
|
|
1204
412
|
provider,
|
|
1205
413
|
nowUnixMs,
|
|
1206
414
|
paths,
|
|
1207
|
-
unlockUntilUnixMs: operation.unlockUntilUnixMs,
|
|
1208
415
|
rpc,
|
|
1209
416
|
walletName,
|
|
417
|
+
context: readContext,
|
|
418
|
+
foundingMessageText: message.text,
|
|
1210
419
|
});
|
|
1211
420
|
workingState = reconciled.state;
|
|
1212
421
|
if (reconciled.resolution === "confirmed" || reconciled.resolution === "live") {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (reconciled.resolution === "ready-for-tx2") {
|
|
1228
|
-
operation = {
|
|
1229
|
-
...operation,
|
|
1230
|
-
targetIdentity: existingTargetIdentity,
|
|
1231
|
-
};
|
|
1232
|
-
resumedFamily = reconciled.family;
|
|
1233
|
-
resumedExisting = true;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
let nextState = workingState;
|
|
1237
|
-
let family;
|
|
1238
|
-
if (resumedFamily !== null) {
|
|
1239
|
-
family = resumedFamily;
|
|
1240
|
-
}
|
|
1241
|
-
else {
|
|
1242
|
-
await confirmAnchor(options.prompter, operation);
|
|
1243
|
-
nextState = reserveAnchorFamilyState(nextState, initialFamily, operation.targetIdentity, operation.foundingMessageText);
|
|
1244
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1245
|
-
const tx1Plan = buildTx1Plan({
|
|
1246
|
-
state: nextState,
|
|
1247
|
-
allUtxos: await rpc.listUnspent(walletName, 1),
|
|
1248
|
-
operation,
|
|
1249
|
-
});
|
|
1250
|
-
const builtTx1 = await buildTx1({
|
|
1251
|
-
rpc,
|
|
1252
|
-
walletName,
|
|
1253
|
-
state: nextState,
|
|
1254
|
-
plan: tx1Plan,
|
|
1255
|
-
});
|
|
1256
|
-
const broadcastingTx1 = createBroadcastingTxRecord(builtTx1);
|
|
1257
|
-
family = {
|
|
1258
|
-
...(findAnchorFamilyByIntent(nextState, initialFamily.intentFingerprintHex) ?? initialFamily),
|
|
1259
|
-
status: "broadcasting",
|
|
1260
|
-
currentStep: "tx1",
|
|
1261
|
-
lastUpdatedAtUnixMs: nowUnixMs,
|
|
1262
|
-
tx1: broadcastingTx1,
|
|
1263
|
-
};
|
|
1264
|
-
nextState = updateAnchorFamilyState({
|
|
1265
|
-
state: nextState,
|
|
1266
|
-
family,
|
|
1267
|
-
target: operation.targetIdentity,
|
|
1268
|
-
status: "broadcasting",
|
|
1269
|
-
localAnchorIntent: "reserved",
|
|
1270
|
-
currentStep: "tx1",
|
|
1271
|
-
nowUnixMs,
|
|
1272
|
-
tx1: broadcastingTx1,
|
|
1273
|
-
});
|
|
1274
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1275
|
-
ensureSameTipHeight(readContext, (await rpc.getBlockchainInfo()).blocks, "wallet_anchor_tip_mismatch");
|
|
1276
|
-
try {
|
|
1277
|
-
await rpc.sendRawTransaction(builtTx1.rawHex);
|
|
1278
|
-
}
|
|
1279
|
-
catch (error) {
|
|
1280
|
-
if (!isAlreadyAcceptedError(error)) {
|
|
1281
|
-
if (isBroadcastUnknownError(error)) {
|
|
1282
|
-
family = {
|
|
1283
|
-
...family,
|
|
1284
|
-
status: "broadcast-unknown",
|
|
1285
|
-
tx1: {
|
|
1286
|
-
...broadcastingTx1,
|
|
1287
|
-
status: "broadcast-unknown",
|
|
1288
|
-
},
|
|
1289
|
-
};
|
|
1290
|
-
nextState = updateAnchorFamilyState({
|
|
1291
|
-
state: nextState,
|
|
1292
|
-
family,
|
|
1293
|
-
target: operation.targetIdentity,
|
|
1294
|
-
status: "broadcast-unknown",
|
|
1295
|
-
localAnchorIntent: "reserved",
|
|
1296
|
-
currentStep: "tx1",
|
|
1297
|
-
nowUnixMs,
|
|
1298
|
-
tx1: family.tx1,
|
|
1299
|
-
});
|
|
1300
|
-
await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1301
|
-
throw new Error("wallet_anchor_tx1_broadcast_unknown");
|
|
1302
|
-
}
|
|
1303
|
-
await unlockTemporaryBuilderLocks(rpc, walletName, builtTx1.temporaryBuilderLockedOutpoints);
|
|
1304
|
-
family = {
|
|
1305
|
-
...family,
|
|
1306
|
-
status: "canceled",
|
|
1307
|
-
tx1: {
|
|
1308
|
-
...broadcastingTx1,
|
|
1309
|
-
status: "canceled",
|
|
1310
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1311
|
-
},
|
|
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,
|
|
1312
436
|
};
|
|
1313
|
-
nextState = updateAnchorFamilyState({
|
|
1314
|
-
state: nextState,
|
|
1315
|
-
family,
|
|
1316
|
-
target: operation.targetIdentity,
|
|
1317
|
-
status: "canceled",
|
|
1318
|
-
localAnchorIntent: "none",
|
|
1319
|
-
currentStep: "tx1",
|
|
1320
|
-
nowUnixMs,
|
|
1321
|
-
tx1: family.tx1,
|
|
1322
|
-
});
|
|
1323
|
-
await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1324
|
-
throw error;
|
|
1325
437
|
}
|
|
438
|
+
replacementFixedInputs = reuse.replacementFixedInputs;
|
|
1326
439
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
...family,
|
|
1330
|
-
status: "live",
|
|
1331
|
-
currentStep: "tx1",
|
|
1332
|
-
tx1: {
|
|
1333
|
-
...broadcastingTx1,
|
|
1334
|
-
status: "live",
|
|
1335
|
-
temporaryBuilderLockedOutpoints: [],
|
|
1336
|
-
},
|
|
1337
|
-
};
|
|
1338
|
-
nextState = updateAnchorFamilyState({
|
|
1339
|
-
state: nextState,
|
|
1340
|
-
family,
|
|
1341
|
-
target: operation.targetIdentity,
|
|
1342
|
-
status: "live",
|
|
1343
|
-
localAnchorIntent: "tx1-live",
|
|
1344
|
-
currentStep: "tx1",
|
|
1345
|
-
nowUnixMs,
|
|
1346
|
-
tx1: family.tx1,
|
|
1347
|
-
listingCancelCommitted: operation.hadListing,
|
|
1348
|
-
moveOwnershipToTarget: true,
|
|
1349
|
-
});
|
|
1350
|
-
nextState = await saveState(nextState, provider, operation.unlockUntilUnixMs, nowUnixMs, paths);
|
|
1351
|
-
if (operation.sourceAnchorOutpoint !== null) {
|
|
1352
|
-
await relockAnchorOutpoint(rpc, walletName, {
|
|
1353
|
-
txid: builtTx1.txid,
|
|
1354
|
-
vout: 2,
|
|
1355
|
-
});
|
|
440
|
+
if (reconciled.resolution === "repair-required") {
|
|
441
|
+
throw new Error("wallet_anchor_repair_required");
|
|
1356
442
|
}
|
|
1357
443
|
}
|
|
1358
|
-
|
|
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({
|
|
1359
458
|
state: nextState,
|
|
1360
|
-
family,
|
|
1361
|
-
operation,
|
|
1362
|
-
readContext: operation.readContext,
|
|
1363
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({
|
|
1364
470
|
rpc,
|
|
1365
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,
|
|
1366
498
|
nowUnixMs,
|
|
1367
499
|
paths,
|
|
1368
|
-
unlockUntilUnixMs: operation.unlockUntilUnixMs,
|
|
1369
500
|
});
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
}
|
|
1376
|
-
finally {
|
|
1377
|
-
await readContext.close();
|
|
1378
|
-
await miningPreemption.release();
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
finally {
|
|
1382
|
-
await controlLock.release();
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
export async function clearPendingAnchor(options) {
|
|
1386
|
-
const provider = options.provider ?? createDefaultWalletSecretProvider();
|
|
1387
|
-
const nowUnixMs = options.nowUnixMs ?? Date.now();
|
|
1388
|
-
const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
|
|
1389
|
-
const controlLock = await acquireFileLock(paths.walletControlLockPath, {
|
|
1390
|
-
purpose: "wallet-anchor-clear",
|
|
1391
|
-
walletRootId: null,
|
|
1392
|
-
});
|
|
1393
|
-
const normalizedDomainName = normalizeDomainName(options.domainName);
|
|
1394
|
-
try {
|
|
1395
|
-
const miningPreemption = await pauseMiningForWalletMutation({
|
|
1396
|
-
paths,
|
|
1397
|
-
reason: "wallet-anchor-clear",
|
|
1398
|
-
});
|
|
1399
|
-
const readContext = await (options.openReadContext ?? openWalletReadContext)({
|
|
1400
|
-
dataDir: options.dataDir,
|
|
1401
|
-
databasePath: options.databasePath,
|
|
1402
|
-
secretProvider: provider,
|
|
1403
|
-
walletControlLockHeld: true,
|
|
1404
|
-
paths,
|
|
1405
|
-
});
|
|
1406
|
-
try {
|
|
1407
|
-
assertWalletMutationContextReady(readContext, "wallet_anchor_clear");
|
|
1408
|
-
const family = findActiveAnchorFamilyByDomain(readContext.localState.state, normalizedDomainName);
|
|
1409
|
-
const domain = readContext.localState.state.domains.find((entry) => entry.name === normalizedDomainName) ?? null;
|
|
1410
|
-
if (domain === null && family === null) {
|
|
1411
|
-
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;
|
|
1412
506
|
}
|
|
1413
|
-
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
507
|
+
catch (error) {
|
|
508
|
+
if (isAlreadyAcceptedError(error)) {
|
|
509
|
+
accepted = true;
|
|
1416
510
|
}
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
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;
|
|
1419
539
|
}
|
|
1420
|
-
return {
|
|
1421
|
-
domainName: normalizedDomainName,
|
|
1422
|
-
cleared: false,
|
|
1423
|
-
previousFamilyStatus: null,
|
|
1424
|
-
previousFamilyStep: null,
|
|
1425
|
-
releasedDedicatedIndex: null,
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
if (family.type !== "anchor") {
|
|
1429
|
-
throw new Error("wallet_anchor_clear_inconsistent_state");
|
|
1430
|
-
}
|
|
1431
|
-
if (family.status !== "draft" || family.currentStep !== "reserved") {
|
|
1432
|
-
throw new Error(`wallet_anchor_clear_not_clearable_${family.status}`);
|
|
1433
540
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|| family.tx1?.attemptedTxid !== null
|
|
1437
|
-
|| family.tx2?.attemptedTxid !== null
|
|
1438
|
-
|| (domain !== null
|
|
1439
|
-
&& (domain.localAnchorIntent !== "reserved"
|
|
1440
|
-
|| domain.dedicatedIndex === null
|
|
1441
|
-
|| domain.dedicatedIndex !== reservedDedicatedIndex))) {
|
|
1442
|
-
throw new Error("wallet_anchor_clear_inconsistent_state");
|
|
541
|
+
if (!accepted) {
|
|
542
|
+
throw new Error("wallet_anchor_broadcast_failed");
|
|
1443
543
|
}
|
|
1444
|
-
await
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1447
|
-
|
|
544
|
+
await unlockTemporaryBuilderLocks(rpc, walletName, built.temporaryBuilderLockedOutpoints);
|
|
545
|
+
const finalStatus = anchorConfirmedOnSnapshot({
|
|
546
|
+
snapshot: readContext.snapshot,
|
|
547
|
+
state: nextState,
|
|
1448
548
|
domainName: normalizedDomainName,
|
|
1449
|
-
|
|
549
|
+
}) ? "confirmed" : "live";
|
|
550
|
+
const finalMutation = updateMutationRecord(broadcastingMutation, finalStatus, nowUnixMs, {
|
|
551
|
+
attemptedTxid: built.txid,
|
|
552
|
+
attemptedWtxid: built.wtxid,
|
|
553
|
+
temporaryBuilderLockedOutpoints: [],
|
|
1450
554
|
});
|
|
1451
|
-
|
|
1452
|
-
state:
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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,
|
|
1457
563
|
provider,
|
|
1458
|
-
unlockUntilUnixMs: readContext.localState.unlockUntilUnixMs,
|
|
1459
564
|
nowUnixMs,
|
|
1460
565
|
paths,
|
|
1461
566
|
});
|
|
1462
567
|
return {
|
|
1463
568
|
domainName: normalizedDomainName,
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
569
|
+
txid: built.txid,
|
|
570
|
+
status: finalStatus,
|
|
571
|
+
reusedExisting: false,
|
|
572
|
+
foundingMessageText: message.text,
|
|
573
|
+
fees: createBuiltWalletMutationFeeSummary({
|
|
574
|
+
selection: feeSelection,
|
|
575
|
+
built,
|
|
576
|
+
}),
|
|
1468
577
|
};
|
|
1469
578
|
}
|
|
1470
579
|
finally {
|