@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
|
@@ -54,6 +54,19 @@ async function setBitcoinSyncProgress(dependencies, info, targetHeightCap) {
|
|
|
54
54
|
: "Reading blocks from the managed Bitcoin node.",
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
function isMissingBlockRecordError(error) {
|
|
58
|
+
return error instanceof Error && error.message.startsWith("client_store_missing_block_record_");
|
|
59
|
+
}
|
|
60
|
+
async function setDeepRecoveryProgress(dependencies, blocks, bestHeight, message) {
|
|
61
|
+
await dependencies.progress.setPhase("cogcoin_sync", {
|
|
62
|
+
blocks,
|
|
63
|
+
headers: bestHeight,
|
|
64
|
+
targetHeight: bestHeight,
|
|
65
|
+
etaSeconds: estimateEtaSeconds(dependencies.cogcoinRateTracker, blocks, bestHeight),
|
|
66
|
+
lastError: null,
|
|
67
|
+
message,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
57
70
|
function resolveIndexedHeightForReplayWindow(tip, startHeight) {
|
|
58
71
|
return tip?.height ?? (startHeight - 1);
|
|
59
72
|
}
|
|
@@ -100,20 +113,50 @@ async function runWithManagedRpcRetry(dependencies, retryState, operation) {
|
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
async function findCommonAncestor(dependencies, tip, bestHeight, runRpc) {
|
|
103
|
-
const
|
|
104
|
-
for (let height =
|
|
116
|
+
const searchStartHeight = Math.min(tip.height, bestHeight);
|
|
117
|
+
for (let height = searchStartHeight; height >= dependencies.startHeight; height -= 1) {
|
|
105
118
|
const localHashHex = height === tip.height
|
|
106
119
|
? tip.blockHashHex
|
|
107
120
|
: (await dependencies.store.loadBlockRecord(height))?.blockHashHex ?? null;
|
|
108
121
|
if (localHashHex === null) {
|
|
109
|
-
|
|
122
|
+
return {
|
|
123
|
+
kind: "checkpoint_recovery",
|
|
124
|
+
};
|
|
110
125
|
}
|
|
111
126
|
const chainHashHex = await runRpc(() => dependencies.rpc.getBlockHash(height));
|
|
112
127
|
if (chainHashHex === localHashHex) {
|
|
113
|
-
return
|
|
128
|
+
return {
|
|
129
|
+
kind: "rewind",
|
|
130
|
+
rewindTarget: height,
|
|
131
|
+
};
|
|
114
132
|
}
|
|
115
133
|
}
|
|
116
|
-
return
|
|
134
|
+
return {
|
|
135
|
+
kind: "rewind",
|
|
136
|
+
rewindTarget: dependencies.startHeight - 1,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function recoverFromCheckpoint(dependencies, startTip, bestHeight, runRpc) {
|
|
140
|
+
let checkpoint = await dependencies.store.loadLatestCheckpointAtOrBelow(Math.min(startTip.height, bestHeight));
|
|
141
|
+
while (checkpoint !== null) {
|
|
142
|
+
const currentCheckpoint = checkpoint;
|
|
143
|
+
const chainHashHex = await runRpc(() => dependencies.rpc.getBlockHash(currentCheckpoint.height));
|
|
144
|
+
if (chainHashHex === currentCheckpoint.blockHashHex) {
|
|
145
|
+
await setDeepRecoveryProgress(dependencies, currentCheckpoint.height, bestHeight, `Retained rewind window exhausted; restoring checkpoint at height ${currentCheckpoint.height.toLocaleString()} and replaying.`);
|
|
146
|
+
await dependencies.client.restoreCheckpoint(currentCheckpoint);
|
|
147
|
+
return {
|
|
148
|
+
rewoundBlocks: startTip.height - currentCheckpoint.height,
|
|
149
|
+
commonAncestorHeight: currentCheckpoint.height,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
checkpoint = await dependencies.store.loadLatestCheckpointAtOrBelow(currentCheckpoint.height - 1);
|
|
153
|
+
}
|
|
154
|
+
await setDeepRecoveryProgress(dependencies, Math.max(0, dependencies.startHeight - 1), bestHeight, "Retained rewind window exhausted; resetting to the processing start and replaying.");
|
|
155
|
+
await dependencies.client.resetToInitialState();
|
|
156
|
+
return {
|
|
157
|
+
rewoundBlocks: startTip.height - dependencies.startHeight + 1,
|
|
158
|
+
commonAncestorHeight: null,
|
|
159
|
+
};
|
|
117
160
|
}
|
|
118
161
|
async function syncAgainstBestHeight(dependencies, bestHeight, runRpc) {
|
|
119
162
|
if (bestHeight < dependencies.startHeight) {
|
|
@@ -127,11 +170,26 @@ async function syncAgainstBestHeight(dependencies, bestHeight, runRpc) {
|
|
|
127
170
|
let rewoundBlocks = 0;
|
|
128
171
|
let commonAncestorHeight = null;
|
|
129
172
|
if (startTip !== null) {
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
173
|
+
const ancestor = await findCommonAncestor(dependencies, startTip, bestHeight, runRpc);
|
|
174
|
+
if (ancestor.kind === "checkpoint_recovery") {
|
|
175
|
+
const recovered = await recoverFromCheckpoint(dependencies, startTip, bestHeight, runRpc);
|
|
176
|
+
rewoundBlocks = recovered.rewoundBlocks;
|
|
177
|
+
commonAncestorHeight = recovered.commonAncestorHeight;
|
|
178
|
+
}
|
|
179
|
+
else if (ancestor.rewindTarget < startTip.height) {
|
|
180
|
+
try {
|
|
181
|
+
commonAncestorHeight = ancestor.rewindTarget < dependencies.startHeight ? null : ancestor.rewindTarget;
|
|
182
|
+
await dependencies.client.rewindToHeight(ancestor.rewindTarget);
|
|
183
|
+
rewoundBlocks = startTip.height - ancestor.rewindTarget;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!isMissingBlockRecordError(error)) {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
const recovered = await recoverFromCheckpoint(dependencies, startTip, bestHeight, runRpc);
|
|
190
|
+
rewoundBlocks = recovered.rewoundBlocks;
|
|
191
|
+
commonAncestorHeight = recovered.commonAncestorHeight;
|
|
192
|
+
}
|
|
135
193
|
}
|
|
136
194
|
}
|
|
137
195
|
const tipAfterRewind = await dependencies.client.getTip();
|
|
@@ -165,6 +223,7 @@ export async function syncToTip(dependencies) {
|
|
|
165
223
|
const runRpc = (operation) => runWithManagedRpcRetry(dependencies, retryState, operation);
|
|
166
224
|
throwIfAborted(dependencies.abortSignal);
|
|
167
225
|
await runRpc(() => dependencies.node.validate());
|
|
226
|
+
await dependencies.bootstrap.cleanupObsoleteSnapshotFilesIfNeeded().catch(() => false);
|
|
168
227
|
const indexedTipBeforeBootstrap = await dependencies.client.getTip();
|
|
169
228
|
await runRpc(() => dependencies.bootstrap.ensureReady(indexedTipBeforeBootstrap, dependencies.node.expectedChain, {
|
|
170
229
|
signal: dependencies.abortSignal,
|
|
@@ -219,6 +278,7 @@ export async function syncToTip(dependencies) {
|
|
|
219
278
|
&& dependencies.targetHeightCap !== undefined
|
|
220
279
|
&& endBestHeight >= dependencies.targetHeightCap;
|
|
221
280
|
if (reachedTargetHeightCap && caughtUpCogcoin) {
|
|
281
|
+
await dependencies.bootstrap.cleanupObsoleteSnapshotFilesIfNeeded().catch(() => false);
|
|
222
282
|
return aggregate;
|
|
223
283
|
}
|
|
224
284
|
if (dependencies.targetHeightCap === null
|
|
@@ -234,8 +294,9 @@ export async function syncToTip(dependencies) {
|
|
|
234
294
|
lastError: null,
|
|
235
295
|
message: dependencies.isFollowing()
|
|
236
296
|
? "Following the live Bitcoin tip."
|
|
237
|
-
: "
|
|
297
|
+
: "Sync complete.",
|
|
238
298
|
});
|
|
299
|
+
await dependencies.bootstrap.cleanupObsoleteSnapshotFilesIfNeeded().catch(() => false);
|
|
239
300
|
return aggregate;
|
|
240
301
|
}
|
|
241
302
|
if (endBestHeight >= dependencies.startHeight && finalTip?.height !== endBestHeight) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function displayHashHexToInternalBytes(hashHex: string): Uint8Array;
|
|
2
|
+
export declare function displayHashHexToInternalHex(hashHex: string): string;
|
|
3
|
+
export declare function internalBytesToDisplayHashHex(hash: Uint8Array): string;
|
|
4
|
+
export declare function internalHashHexToDisplayHashHex(hashHex: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes, reverseBytes } from "../bytes.js";
|
|
2
|
+
export function displayHashHexToInternalBytes(hashHex) {
|
|
3
|
+
return reverseBytes(hexToBytes(hashHex));
|
|
4
|
+
}
|
|
5
|
+
export function displayHashHexToInternalHex(hashHex) {
|
|
6
|
+
return bytesToHex(displayHashHexToInternalBytes(hashHex));
|
|
7
|
+
}
|
|
8
|
+
export function internalBytesToDisplayHashHex(hash) {
|
|
9
|
+
return bytesToHex(reverseBytes(hash));
|
|
10
|
+
}
|
|
11
|
+
export function internalHashHexToDisplayHashHex(hashHex) {
|
|
12
|
+
return internalBytesToDisplayHashHex(hexToBytes(hashHex));
|
|
13
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import net from "node:net";
|
|
3
3
|
import { access, constants, mkdir, readFile, rm } from "node:fs/promises";
|
|
4
|
-
import { serializeIndexerState } from "@cogcoin/indexer";
|
|
4
|
+
import { loadBundledGenesisParameters, serializeIndexerState } from "@cogcoin/indexer";
|
|
5
5
|
import { openManagedBitcoindClientInternal } from "./client.js";
|
|
6
6
|
import { openClient } from "../client.js";
|
|
7
7
|
import { openSqliteStore } from "../sqlite/index.js";
|
|
8
8
|
import { writeRuntimeStatusFile } from "../wallet/fs/status-file.js";
|
|
9
9
|
import { createRpcClient } from "./node.js";
|
|
10
|
+
import { normalizeCogcoinProcessingStartHeight } from "./processing-start-height.js";
|
|
10
11
|
import { resolveManagedServicePaths, UNINITIALIZED_WALLET_ROOT_ID } from "./service-paths.js";
|
|
11
12
|
import { INDEXER_DAEMON_SCHEMA_VERSION, INDEXER_DAEMON_SERVICE_API_VERSION, } from "./types.js";
|
|
12
13
|
const SNAPSHOT_TTL_MS = 30_000;
|
|
@@ -156,6 +157,7 @@ async function main() {
|
|
|
156
157
|
const paths = resolveManagedServicePaths(dataDir, walletRootId);
|
|
157
158
|
const daemonInstanceId = randomUUID();
|
|
158
159
|
const binaryVersion = await readPackageVersionFromDisk();
|
|
160
|
+
const genesisParameters = await loadBundledGenesisParameters();
|
|
159
161
|
const startedAtUnixMs = Date.now();
|
|
160
162
|
const snapshots = new Map();
|
|
161
163
|
let state = "starting";
|
|
@@ -292,12 +294,18 @@ async function main() {
|
|
|
292
294
|
backgroundResumePromise = (async () => {
|
|
293
295
|
const bitcoindStatus = await readManagedBitcoindStatus(paths);
|
|
294
296
|
const store = await openSqliteStore({ filename: databasePath });
|
|
297
|
+
const chain = bitcoindStatus?.chain ?? "main";
|
|
298
|
+
const startHeight = normalizeCogcoinProcessingStartHeight({
|
|
299
|
+
chain,
|
|
300
|
+
startHeight: bitcoindStatus?.startHeight,
|
|
301
|
+
genesisParameters,
|
|
302
|
+
});
|
|
295
303
|
try {
|
|
296
304
|
const client = await openManagedBitcoindClientInternal({
|
|
297
305
|
store,
|
|
298
306
|
dataDir,
|
|
299
|
-
chain
|
|
300
|
-
startHeight
|
|
307
|
+
chain,
|
|
308
|
+
startHeight,
|
|
301
309
|
walletRootId,
|
|
302
310
|
progressOutput: "none",
|
|
303
311
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hexToBytes } from "../bytes.js";
|
|
2
|
+
import { displayHashHexToInternalBytes } from "./hash-order.js";
|
|
2
3
|
function btcValueToSats(value) {
|
|
3
4
|
const source = typeof value === "number"
|
|
4
5
|
? value.toFixed(8)
|
|
@@ -40,8 +41,8 @@ function normalizeTransaction(transaction) {
|
|
|
40
41
|
export function normalizeRpcBlock(block) {
|
|
41
42
|
return {
|
|
42
43
|
height: block.height,
|
|
43
|
-
hash:
|
|
44
|
-
previousHash: block.previousblockhash ?
|
|
44
|
+
hash: displayHashHexToInternalBytes(block.hash),
|
|
45
|
+
previousHash: block.previousblockhash ? displayHashHexToInternalBytes(block.previousblockhash) : null,
|
|
45
46
|
transactions: block.tx.map(normalizeTransaction),
|
|
46
47
|
};
|
|
47
48
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { GenesisParameters } from "@cogcoin/indexer/types";
|
|
2
2
|
export declare function resolveCogcoinProcessingStartHeight(genesisParameters: GenesisParameters): number;
|
|
3
|
+
export declare function normalizeCogcoinProcessingStartHeight(options: {
|
|
4
|
+
chain: "main" | "regtest";
|
|
5
|
+
startHeight: number | null | undefined;
|
|
6
|
+
genesisParameters: GenesisParameters;
|
|
7
|
+
}): number;
|
|
3
8
|
export declare function assertCogcoinProcessingStartHeight(options: {
|
|
4
9
|
chain: "main" | "regtest";
|
|
5
10
|
startHeight: number;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export function resolveCogcoinProcessingStartHeight(genesisParameters) {
|
|
2
2
|
return genesisParameters.genesisBlock;
|
|
3
3
|
}
|
|
4
|
+
export function normalizeCogcoinProcessingStartHeight(options) {
|
|
5
|
+
if (options.chain !== "main") {
|
|
6
|
+
return options.startHeight ?? 0;
|
|
7
|
+
}
|
|
8
|
+
const processingStartHeight = resolveCogcoinProcessingStartHeight(options.genesisParameters);
|
|
9
|
+
return Math.max(options.startHeight ?? processingStartHeight, processingStartHeight);
|
|
10
|
+
}
|
|
4
11
|
export function assertCogcoinProcessingStartHeight(options) {
|
|
5
12
|
const processingStartHeight = resolveCogcoinProcessingStartHeight(options.genesisParameters);
|
|
6
13
|
if (options.chain === "main" && options.startHeight < processingStartHeight) {
|
|
@@ -7,6 +7,10 @@ export declare const INTRO_ENTRY_MS = 5000;
|
|
|
7
7
|
export declare const INTRO_PAUSE_MS = 5000;
|
|
8
8
|
export declare const INTRO_EXIT_MS = 5000;
|
|
9
9
|
export declare const INTRO_TOTAL_MS: number;
|
|
10
|
+
export declare const COMPLETION_ENTRY_MS = 5000;
|
|
11
|
+
export declare const COMPLETION_PAUSE_MS = 5000;
|
|
12
|
+
export declare const COMPLETION_EXIT_MS = 0;
|
|
13
|
+
export declare const COMPLETION_TOTAL_MS: number;
|
|
10
14
|
export declare const STATUS_ELLIPSIS_WIDTH = 3;
|
|
11
15
|
export declare const STATUS_ELLIPSIS_TICK_MS = 500;
|
|
12
16
|
export declare const NEUTRAL_MESSAGE_TITLE = "\u26ED C O G C O I N \u26ED";
|
|
@@ -7,6 +7,10 @@ export const INTRO_ENTRY_MS = 5_000;
|
|
|
7
7
|
export const INTRO_PAUSE_MS = 5_000;
|
|
8
8
|
export const INTRO_EXIT_MS = 5_000;
|
|
9
9
|
export const INTRO_TOTAL_MS = INTRO_ENTRY_MS + INTRO_PAUSE_MS + INTRO_EXIT_MS;
|
|
10
|
+
export const COMPLETION_ENTRY_MS = INTRO_ENTRY_MS;
|
|
11
|
+
export const COMPLETION_PAUSE_MS = INTRO_PAUSE_MS;
|
|
12
|
+
export const COMPLETION_EXIT_MS = 0;
|
|
13
|
+
export const COMPLETION_TOTAL_MS = COMPLETION_ENTRY_MS + COMPLETION_PAUSE_MS + COMPLETION_EXIT_MS;
|
|
10
14
|
export const STATUS_ELLIPSIS_WIDTH = 3;
|
|
11
15
|
export const STATUS_ELLIPSIS_TICK_MS = 500;
|
|
12
16
|
export const NEUTRAL_MESSAGE_TITLE = "⛭ C O G C O I N ⛭";
|
|
@@ -2,6 +2,7 @@ import type { QuoteDisplayPhase } from "../quotes.js";
|
|
|
2
2
|
import type { BootstrapPhase, BootstrapProgress, ManagedBitcoindProgressEvent, ProgressOutputMode, SnapshotMetadata, WritingQuote } from "../types.js";
|
|
3
3
|
import { type FollowSceneStateForTesting } from "./follow-scene.js";
|
|
4
4
|
import { type RenderClock, type TtyRenderStream } from "./render-policy.js";
|
|
5
|
+
import { type FollowSceneRenderOptions } from "./tty-renderer.js";
|
|
5
6
|
interface QuoteRotatorLike {
|
|
6
7
|
current(now?: number): Promise<{
|
|
7
8
|
displayPhase: QuoteDisplayPhase;
|
|
@@ -12,7 +13,7 @@ interface QuoteRotatorLike {
|
|
|
12
13
|
interface ProgressRendererLike {
|
|
13
14
|
render(displayPhase: QuoteDisplayPhase, quote: WritingQuote | null, progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, introElapsedMs?: number, statusFieldText?: string): void;
|
|
14
15
|
renderTrainScene(kind: "intro" | "completion", progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, elapsedMs: number, statusFieldText?: string): void;
|
|
15
|
-
renderFollowScene(progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, followScene: FollowSceneStateForTesting, statusFieldText?: string): void;
|
|
16
|
+
renderFollowScene(progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, followScene: FollowSceneStateForTesting, statusFieldText?: string, renderOptions?: FollowSceneRenderOptions): void;
|
|
16
17
|
close(): void;
|
|
17
18
|
}
|
|
18
19
|
interface ProgressControllerOptions {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WritingQuoteRotator as QuoteRotator } from "../quotes.js";
|
|
2
|
-
import {
|
|
2
|
+
import { COMPLETION_TOTAL_MS } from "./constants.js";
|
|
3
3
|
import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, setFollowBlockTime, syncFollowSceneState, } from "./follow-scene.js";
|
|
4
4
|
import { createBootstrapProgress, createDefaultMessage, resolveStatusFieldText, } from "./formatting.js";
|
|
5
5
|
import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "./render-policy.js";
|
|
@@ -105,9 +105,9 @@ export class ManagedProgressController {
|
|
|
105
105
|
}
|
|
106
106
|
const startedAt = this.#clock.now();
|
|
107
107
|
while (true) {
|
|
108
|
-
const elapsedMs = Math.min(
|
|
108
|
+
const elapsedMs = Math.min(COMPLETION_TOTAL_MS, this.#clock.now() - startedAt);
|
|
109
109
|
this.#renderer.renderTrainScene("completion", this.#progress, this.#cogcoinSyncHeight, this.#cogcoinSyncTargetHeight, elapsedMs);
|
|
110
|
-
if (elapsedMs >=
|
|
110
|
+
if (elapsedMs >= COMPLETION_TOTAL_MS) {
|
|
111
111
|
break;
|
|
112
112
|
}
|
|
113
113
|
await new Promise((resolve) => {
|
|
@@ -16,6 +16,10 @@ export interface FollowSceneStateForTesting {
|
|
|
16
16
|
pendingStaticX: number | null;
|
|
17
17
|
animation: FollowAnimation | null;
|
|
18
18
|
}
|
|
19
|
+
export interface FollowFrameRenderOptions {
|
|
20
|
+
artworkCogText?: string | null;
|
|
21
|
+
artworkSatText?: string | null;
|
|
22
|
+
}
|
|
19
23
|
export declare function createFollowSceneState(indexedHeight?: number | null, blockTimesByHeight?: Record<number, number>): FollowSceneStateForTesting;
|
|
20
24
|
export declare function setFollowBlockTime(state: FollowSceneStateForTesting, height: number, blockTime: number): void;
|
|
21
25
|
export declare function replaceFollowBlockTimes(state: FollowSceneStateForTesting, blockTimesByHeight: Record<number, number>): void;
|
|
@@ -27,7 +31,7 @@ export declare function syncFollowSceneState(state: FollowSceneStateForTesting,
|
|
|
27
31
|
liveActivated?: boolean;
|
|
28
32
|
}): void;
|
|
29
33
|
export declare function advanceFollowSceneState(state: FollowSceneStateForTesting, now: number): void;
|
|
30
|
-
export declare function renderFollowFrame(state: FollowSceneStateForTesting, statusFieldText: string, now: number): string[];
|
|
34
|
+
export declare function renderFollowFrame(state: FollowSceneStateForTesting, statusFieldText: string, now: number, options?: FollowFrameRenderOptions): string[];
|
|
31
35
|
export declare function createFollowSceneStateForTesting(indexedHeight?: number | null, blockTimesByHeight?: Record<number, number>): FollowSceneStateForTesting;
|
|
32
36
|
export declare function setFollowBlockTimesForTesting(state: FollowSceneStateForTesting, blockTimesByHeight: Record<number, number>): FollowSceneStateForTesting;
|
|
33
37
|
export declare function setFollowBlockTimeForTesting(state: FollowSceneStateForTesting, height: number, blockTime: number): FollowSceneStateForTesting;
|
|
@@ -37,4 +41,4 @@ export declare function syncFollowSceneStateForTesting(state: FollowSceneStateFo
|
|
|
37
41
|
liveActivated?: boolean;
|
|
38
42
|
}): FollowSceneStateForTesting;
|
|
39
43
|
export declare function advanceFollowSceneStateForTesting(state: FollowSceneStateForTesting, now: number): FollowSceneStateForTesting;
|
|
40
|
-
export declare function renderFollowFrameForTesting(state: FollowSceneStateForTesting, statusFieldText?: string, now?: number): string[];
|
|
44
|
+
export declare function renderFollowFrameForTesting(state: FollowSceneStateForTesting, statusFieldText?: string, now?: number, options?: FollowFrameRenderOptions): string[];
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { loadArtTemplate, loadFollowCarTemplate } from "./assets.js";
|
|
2
|
-
import { FOLLOW_AGE_ROW, FOLLOW_APPROACH_MS, FOLLOW_CAR_HEIGHT, FOLLOW_CAR_PITCH, FOLLOW_CAR_TOP, FOLLOW_CAR_WIDTH, FOLLOW_CENTER_SLOT_X, FOLLOW_CLIP_MAX_COLUMN, FOLLOW_CLIP_MIN_COLUMN, FOLLOW_CONNECTION_SLOT_X, FOLLOW_FAST_APPROACH_MS, FOLLOW_FAST_SHIFT_MS, FOLLOW_PENDING_ENTER_MS, FOLLOW_PENDING_LABEL, FOLLOW_PENDING_OFFSCREEN_LEFT_X, FOLLOW_PENDING_SLOT_X, FOLLOW_RIGHT_SLOT_XS, FOLLOW_SHIFT_MS, FOLLOW_WINDOW_LEFT, MESSAGE_FIELD_ROW, NEUTRAL_MESSAGE_TITLE, STATUS_FIELD_ROW, } from "./constants.js";
|
|
3
|
-
import { centerLine, overlayCenteredField } from "./formatting.js";
|
|
2
|
+
import { FIELD_LEFT, FIELD_WIDTH, FOLLOW_AGE_ROW, FOLLOW_APPROACH_MS, FOLLOW_CAR_HEIGHT, FOLLOW_CAR_PITCH, FOLLOW_CAR_TOP, FOLLOW_CAR_WIDTH, FOLLOW_CENTER_SLOT_X, FOLLOW_CLIP_MAX_COLUMN, FOLLOW_CLIP_MIN_COLUMN, FOLLOW_CONNECTION_SLOT_X, FOLLOW_FAST_APPROACH_MS, FOLLOW_FAST_SHIFT_MS, FOLLOW_PENDING_ENTER_MS, FOLLOW_PENDING_LABEL, FOLLOW_PENDING_OFFSCREEN_LEFT_X, FOLLOW_PENDING_SLOT_X, FOLLOW_RIGHT_SLOT_XS, FOLLOW_SHIFT_MS, FOLLOW_WINDOW_LEFT, MESSAGE_FIELD_ROW, NEUTRAL_MESSAGE_TITLE, STATUS_FIELD_ROW, } from "./constants.js";
|
|
3
|
+
import { centerLine, computeCenteredLeftPadding, overlayCenteredField, replaceSegment, rightAlignLine, truncateLine, } from "./formatting.js";
|
|
4
|
+
const FOLLOW_TITLE_LEFT = computeCenteredLeftPadding(NEUTRAL_MESSAGE_TITLE, FIELD_WIDTH);
|
|
5
|
+
const FOLLOW_TITLE_WIDTH = NEUTRAL_MESSAGE_TITLE.length;
|
|
6
|
+
const FOLLOW_COG_LEFT = 0;
|
|
7
|
+
const FOLLOW_COG_WIDTH = FOLLOW_TITLE_LEFT;
|
|
8
|
+
const FOLLOW_SAT_LEFT = FOLLOW_TITLE_LEFT + FOLLOW_TITLE_WIDTH;
|
|
9
|
+
const FOLLOW_SAT_WIDTH = FIELD_WIDTH - FOLLOW_SAT_LEFT;
|
|
4
10
|
export function createFollowSceneState(indexedHeight = null, blockTimesByHeight = {}) {
|
|
5
11
|
return {
|
|
6
12
|
liveActivated: false,
|
|
@@ -59,6 +65,20 @@ export function formatCompactFollowAgeLabel(blockTime, now) {
|
|
|
59
65
|
return `${Math.floor(elapsedHours / 24)}d`;
|
|
60
66
|
}
|
|
61
67
|
export const formatCompactFollowAgeLabelForTesting = formatCompactFollowAgeLabel;
|
|
68
|
+
function leftAlignLane(line, width) {
|
|
69
|
+
const aligned = truncateLine(line, width);
|
|
70
|
+
return aligned.padEnd(width, " ");
|
|
71
|
+
}
|
|
72
|
+
function renderFollowHeaderField(options) {
|
|
73
|
+
let field = centerLine(NEUTRAL_MESSAGE_TITLE, FIELD_WIDTH);
|
|
74
|
+
if (options.artworkCogText !== null && options.artworkCogText !== undefined && options.artworkCogText.length > 0) {
|
|
75
|
+
field = replaceSegment(field, FOLLOW_COG_LEFT, FOLLOW_COG_WIDTH, leftAlignLane(options.artworkCogText, FOLLOW_COG_WIDTH));
|
|
76
|
+
}
|
|
77
|
+
if (options.artworkSatText !== null && options.artworkSatText !== undefined && options.artworkSatText.length > 0) {
|
|
78
|
+
field = replaceSegment(field, FOLLOW_SAT_LEFT, FOLLOW_SAT_WIDTH, rightAlignLine(options.artworkSatText, FOLLOW_SAT_WIDTH));
|
|
79
|
+
}
|
|
80
|
+
return field;
|
|
81
|
+
}
|
|
62
82
|
function highestTrackedFollowHeight(state) {
|
|
63
83
|
return Math.max(state.indexedHeight ?? Number.NEGATIVE_INFINITY, state.displayedCenterHeight ?? Number.NEGATIVE_INFINITY, state.animation?.height ?? Number.NEGATIVE_INFINITY, ...state.queuedHeights);
|
|
64
84
|
}
|
|
@@ -327,7 +347,7 @@ function resolveFollowCarPlacements(state, now) {
|
|
|
327
347
|
}
|
|
328
348
|
return placements;
|
|
329
349
|
}
|
|
330
|
-
export function renderFollowFrame(state, statusFieldText, now) {
|
|
350
|
+
export function renderFollowFrame(state, statusFieldText, now, options = {}) {
|
|
331
351
|
let frame = createFollowBaseFrame();
|
|
332
352
|
const placements = resolveFollowCarPlacements(state, now);
|
|
333
353
|
for (const car of placements) {
|
|
@@ -339,7 +359,10 @@ export function renderFollowFrame(state, statusFieldText, now) {
|
|
|
339
359
|
frame = overlayFollowAgeLabel(frame, car, ageLabel);
|
|
340
360
|
}
|
|
341
361
|
}
|
|
342
|
-
|
|
362
|
+
const headerRow = frame[MESSAGE_FIELD_ROW];
|
|
363
|
+
if (headerRow !== undefined) {
|
|
364
|
+
frame[MESSAGE_FIELD_ROW] = replaceSegment(headerRow, FIELD_LEFT, FIELD_WIDTH, renderFollowHeaderField(options));
|
|
365
|
+
}
|
|
343
366
|
overlayCenteredField(frame, STATUS_FIELD_ROW, statusFieldText);
|
|
344
367
|
return frame;
|
|
345
368
|
}
|
|
@@ -362,6 +385,6 @@ export function advanceFollowSceneStateForTesting(state, now) {
|
|
|
362
385
|
advanceFollowSceneState(state, now);
|
|
363
386
|
return cloneFollowSceneState(state);
|
|
364
387
|
}
|
|
365
|
-
export function renderFollowFrameForTesting(state, statusFieldText = "", now = 0) {
|
|
366
|
-
return renderFollowFrame(state, statusFieldText, now);
|
|
388
|
+
export function renderFollowFrameForTesting(state, statusFieldText = "", now = 0, options = {}) {
|
|
389
|
+
return renderFollowFrame(state, statusFieldText, now, options);
|
|
367
390
|
}
|
|
@@ -9,6 +9,7 @@ export declare function renderIndeterminateBar(width: number, now: number): stri
|
|
|
9
9
|
export declare function truncateLine(line: string, width: number): string;
|
|
10
10
|
export declare function normalizeInlineText(value: string): string;
|
|
11
11
|
export declare function centerLine(line: string, width: number): string;
|
|
12
|
+
export declare function rightAlignLine(line: string, width: number): string;
|
|
12
13
|
export declare function positionLine(line: string, width: number, leftPadding: number): string;
|
|
13
14
|
export declare function computeCenteredLeftPadding(line: string, width: number): number;
|
|
14
15
|
export declare function centerFieldText(text: string): string;
|
|
@@ -102,6 +102,11 @@ export function centerLine(line, width) {
|
|
|
102
102
|
const leftPadding = Math.max(0, Math.floor((width - centered.length) / 2));
|
|
103
103
|
return `${" ".repeat(leftPadding)}${centered}`.padEnd(width, " ");
|
|
104
104
|
}
|
|
105
|
+
export function rightAlignLine(line, width) {
|
|
106
|
+
const aligned = truncateLine(line, width);
|
|
107
|
+
const leftPadding = Math.max(0, width - aligned.length);
|
|
108
|
+
return `${" ".repeat(leftPadding)}${aligned}`.padEnd(width, " ");
|
|
109
|
+
}
|
|
105
110
|
export function positionLine(line, width, leftPadding) {
|
|
106
111
|
const positioned = truncateLine(line, width);
|
|
107
112
|
const safePadding = Math.max(0, Math.min(leftPadding, Math.max(0, width - positioned.length)));
|
|
@@ -130,6 +135,7 @@ export function resolveStatusFieldText(progress, snapshotHeight, now = 0) {
|
|
|
130
135
|
case "getblock_archive_import":
|
|
131
136
|
return `Importing getblock range${animateStatusEllipsis(now)}`;
|
|
132
137
|
case "paused":
|
|
138
|
+
return `Waiting to start managed sync${animateStatusEllipsis(now)}`;
|
|
133
139
|
case "snapshot_download":
|
|
134
140
|
return `Downloading snapshot to ${snapshotHeight}${animateStatusEllipsis(now)}`;
|
|
135
141
|
case "wait_headers_for_snapshot":
|
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
import { INTRO_ENTRY_MS, INTRO_EXIT_MS, INTRO_PAUSE_MS, INTRO_TOTAL_MS, MESSAGE_FIELD_ROW, NEUTRAL_MESSAGE_TITLE, SCROLL_WINDOW_LEFT, STATUS_FIELD_ROW, TRAIN_CENTER_X, TRAIN_CLIP_MAX_COLUMN, TRAIN_CLIP_MIN_COLUMN, TRAIN_OFFSCREEN_LEFT_X, TRAIN_OFFSCREEN_RIGHT_X, TRAIN_SPRITE_TOP, } from "./constants.js";
|
|
1
|
+
import { COMPLETION_ENTRY_MS, COMPLETION_EXIT_MS, COMPLETION_PAUSE_MS, COMPLETION_TOTAL_MS, INTRO_ENTRY_MS, INTRO_EXIT_MS, INTRO_PAUSE_MS, INTRO_TOTAL_MS, MESSAGE_FIELD_ROW, NEUTRAL_MESSAGE_TITLE, SCROLL_WINDOW_LEFT, STATUS_FIELD_ROW, TRAIN_CENTER_X, TRAIN_CLIP_MAX_COLUMN, TRAIN_CLIP_MIN_COLUMN, TRAIN_OFFSCREEN_LEFT_X, TRAIN_OFFSCREEN_RIGHT_X, TRAIN_SPRITE_TOP, } from "./constants.js";
|
|
2
2
|
import { loadArtTemplate, loadSprite } from "./assets.js";
|
|
3
3
|
import { overlayCenteredField } from "./formatting.js";
|
|
4
|
+
function resolveTrainSceneTimings(kind) {
|
|
5
|
+
return kind === "intro"
|
|
6
|
+
? {
|
|
7
|
+
entryMs: INTRO_ENTRY_MS,
|
|
8
|
+
pauseMs: INTRO_PAUSE_MS,
|
|
9
|
+
exitMs: INTRO_EXIT_MS,
|
|
10
|
+
totalMs: INTRO_TOTAL_MS,
|
|
11
|
+
}
|
|
12
|
+
: {
|
|
13
|
+
entryMs: COMPLETION_ENTRY_MS,
|
|
14
|
+
pauseMs: COMPLETION_PAUSE_MS,
|
|
15
|
+
exitMs: COMPLETION_EXIT_MS,
|
|
16
|
+
totalMs: COMPLETION_TOTAL_MS,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
4
19
|
export function resolveTrainSceneMessage(kind, elapsedMs) {
|
|
5
|
-
|
|
20
|
+
const timings = resolveTrainSceneTimings(kind);
|
|
21
|
+
if (elapsedMs < timings.entryMs) {
|
|
6
22
|
return kind === "intro"
|
|
7
23
|
? "Here comes the mining train!"
|
|
8
24
|
: "Congratuations, you are synced!";
|
|
9
25
|
}
|
|
10
|
-
if (elapsedMs <
|
|
26
|
+
if (elapsedMs < timings.entryMs + timings.pauseMs || kind === "completion") {
|
|
11
27
|
return kind === "intro"
|
|
12
28
|
? "Welcome to Cogcoin!"
|
|
13
29
|
: "You shape your own future.";
|
|
14
30
|
}
|
|
15
|
-
if (elapsedMs <
|
|
31
|
+
if (elapsedMs < timings.totalMs) {
|
|
16
32
|
return kind === "intro"
|
|
17
33
|
? "How many sentences will you mine?"
|
|
18
34
|
: "Your Cogcoin story begins...";
|
|
@@ -25,25 +41,27 @@ export function resolveIntroMessageForTesting(introElapsedMs) {
|
|
|
25
41
|
export function resolveCompletionMessageForTesting(completionElapsedMs) {
|
|
26
42
|
return resolveTrainSceneMessage("completion", completionElapsedMs);
|
|
27
43
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
44
|
+
function resolveTrainSpriteName(kind, elapsedMs) {
|
|
45
|
+
const timings = resolveTrainSceneTimings(kind);
|
|
46
|
+
if (elapsedMs >= timings.entryMs && (kind === "completion" || elapsedMs < timings.entryMs + timings.pauseMs)) {
|
|
30
47
|
return "train";
|
|
31
48
|
}
|
|
32
49
|
return "train-smoke";
|
|
33
50
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
51
|
+
function resolveTrainSpriteX(kind, elapsedMs) {
|
|
52
|
+
const timings = resolveTrainSceneTimings(kind);
|
|
53
|
+
if (elapsedMs <= 0) {
|
|
36
54
|
return TRAIN_OFFSCREEN_RIGHT_X;
|
|
37
55
|
}
|
|
38
|
-
if (
|
|
39
|
-
const progress =
|
|
56
|
+
if (elapsedMs < timings.entryMs) {
|
|
57
|
+
const progress = elapsedMs / timings.entryMs;
|
|
40
58
|
return Math.round(TRAIN_OFFSCREEN_RIGHT_X + ((TRAIN_CENTER_X - TRAIN_OFFSCREEN_RIGHT_X) * progress));
|
|
41
59
|
}
|
|
42
|
-
if (
|
|
60
|
+
if (kind === "completion" || elapsedMs < timings.entryMs + timings.pauseMs) {
|
|
43
61
|
return TRAIN_CENTER_X;
|
|
44
62
|
}
|
|
45
|
-
if (
|
|
46
|
-
const progress = (
|
|
63
|
+
if (elapsedMs < timings.totalMs) {
|
|
64
|
+
const progress = (elapsedMs - timings.entryMs - timings.pauseMs) / timings.exitMs;
|
|
47
65
|
return Math.round(TRAIN_CENTER_X + ((TRAIN_OFFSCREEN_LEFT_X - TRAIN_CENTER_X) * progress));
|
|
48
66
|
}
|
|
49
67
|
return TRAIN_OFFSCREEN_LEFT_X;
|
|
@@ -70,13 +88,14 @@ function overlaySpriteOnFrame(frame, sprite, spriteX) {
|
|
|
70
88
|
}
|
|
71
89
|
export function renderTrainSceneFrame(kind, elapsedMs, statusFieldText) {
|
|
72
90
|
const frame = [...loadArtTemplate("scroll")];
|
|
73
|
-
const
|
|
91
|
+
const timings = resolveTrainSceneTimings(kind);
|
|
92
|
+
const renderedFrame = kind === "intro" && elapsedMs >= timings.totalMs
|
|
74
93
|
? frame
|
|
75
|
-
: overlaySpriteOnFrame(frame, loadSprite(
|
|
94
|
+
: overlaySpriteOnFrame(frame, loadSprite(resolveTrainSpriteName(kind, elapsedMs)), resolveTrainSpriteX(kind, elapsedMs));
|
|
76
95
|
const message = resolveTrainSceneMessage(kind, elapsedMs);
|
|
77
|
-
overlayCenteredField(
|
|
78
|
-
overlayCenteredField(
|
|
79
|
-
return
|
|
96
|
+
overlayCenteredField(renderedFrame, MESSAGE_FIELD_ROW, message.length > 0 ? message : NEUTRAL_MESSAGE_TITLE);
|
|
97
|
+
overlayCenteredField(renderedFrame, STATUS_FIELD_ROW, statusFieldText);
|
|
98
|
+
return renderedFrame;
|
|
80
99
|
}
|
|
81
100
|
export function renderIntroFrame(introElapsedMs, statusFieldText) {
|
|
82
101
|
return renderTrainSceneFrame("intro", introElapsedMs, statusFieldText);
|
|
@@ -7,12 +7,17 @@ interface RenderStream {
|
|
|
7
7
|
columns?: number;
|
|
8
8
|
write(chunk: string): boolean | void;
|
|
9
9
|
}
|
|
10
|
+
export interface FollowSceneRenderOptions {
|
|
11
|
+
artworkCogText?: string | null;
|
|
12
|
+
artworkSatText?: string | null;
|
|
13
|
+
extraLines?: string[];
|
|
14
|
+
}
|
|
10
15
|
export declare class TtyProgressRenderer {
|
|
11
16
|
#private;
|
|
12
17
|
constructor(stream?: RenderStream);
|
|
13
18
|
render(displayPhase: QuoteDisplayPhase, quote: WritingQuote | null, progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, introElapsedMs?: number, statusFieldText?: string): void;
|
|
14
19
|
renderTrainScene(kind: TrainSceneKind, progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, elapsedMs: number, statusFieldText?: string): void;
|
|
15
|
-
renderFollowScene(progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, followScene: FollowSceneStateForTesting, statusFieldText?: string): void;
|
|
20
|
+
renderFollowScene(progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, followScene: FollowSceneStateForTesting, statusFieldText?: string, renderOptions?: FollowSceneRenderOptions): void;
|
|
16
21
|
close(): void;
|
|
17
22
|
}
|
|
18
23
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ART_WIDTH,
|
|
1
|
+
import { ART_WIDTH, NEUTRAL_MESSAGE_TITLE } from "./constants.js";
|
|
2
2
|
import { renderFollowFrame } from "./follow-scene.js";
|
|
3
3
|
import { formatProgressLine, formatQuoteLine, truncateLine } from "./formatting.js";
|
|
4
4
|
import { renderArtFrame } from "./quote-scene.js";
|
|
@@ -85,13 +85,17 @@ export class TtyProgressRenderer {
|
|
|
85
85
|
this.#writeChunk(frame);
|
|
86
86
|
this.#previousFrameHeight = lines.length;
|
|
87
87
|
}
|
|
88
|
-
renderFollowScene(progress, cogcoinSyncHeight, cogcoinSyncTargetHeight, followScene, statusFieldText = "") {
|
|
88
|
+
renderFollowScene(progress, cogcoinSyncHeight, cogcoinSyncTargetHeight, followScene, statusFieldText = "", renderOptions = {}) {
|
|
89
89
|
const now = Date.now();
|
|
90
90
|
const width = Math.max(20, this.#stream.columns ?? 120);
|
|
91
91
|
const progressLine = formatProgressLine(progress, cogcoinSyncHeight, cogcoinSyncTargetHeight, width, now);
|
|
92
|
+
const extraLines = (renderOptions.extraLines ?? []).map((line) => truncateLine(line, width));
|
|
92
93
|
const lines = width >= ART_WIDTH
|
|
93
|
-
? [...renderFollowFrame(followScene, statusFieldText, now
|
|
94
|
-
|
|
94
|
+
? [...renderFollowFrame(followScene, statusFieldText, now, {
|
|
95
|
+
artworkCogText: renderOptions.artworkCogText ?? null,
|
|
96
|
+
artworkSatText: renderOptions.artworkSatText ?? null,
|
|
97
|
+
}), "", progressLine, "", ...extraLines]
|
|
98
|
+
: [truncateLine(NEUTRAL_MESSAGE_TITLE, width), progressLine, "", ...extraLines];
|
|
95
99
|
const frame = lines.join("\n");
|
|
96
100
|
this.#resetFrameIfExternalWritesDetected();
|
|
97
101
|
if (!this.#rendered) {
|
package/dist/bitcoind/rpc.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RpcBlock, RpcBlockchainInfo, RpcChainStatesResponse, RpcCreateWalletResult, RpcDecodedPsbt, RpcDescriptorInfo, RpcFinalizePsbtResult, RpcImportDescriptorRequest, RpcImportDescriptorResult, RpcListUnspentEntry, RpcMempoolEntry, RpcMempoolInfo, RpcRawMempoolVerbose, RpcListDescriptorsResult, RpcLockedUnspent, RpcLoadTxOutSetResult, RpcLoadWalletResult, RpcNetworkInfo, RpcTestMempoolAcceptResult, RpcWalletInfo, RpcWalletCreateFundedPsbtResult, RpcWalletProcessPsbtResult, RpcTransaction, RpcWalletTransaction, RpcZmqNotification } from "./types.js";
|
|
1
|
+
import type { RpcBlock, RpcBlockchainInfo, RpcChainStatesResponse, RpcCreateWalletResult, RpcDecodedPsbt, RpcDescriptorInfo, RpcEstimateSmartFeeResult, RpcFinalizePsbtResult, RpcImportDescriptorRequest, RpcImportDescriptorResult, RpcListUnspentEntry, RpcMempoolEntry, RpcMempoolInfo, RpcRawMempoolVerbose, RpcListDescriptorsResult, RpcLockedUnspent, RpcLoadTxOutSetResult, RpcLoadWalletResult, RpcNetworkInfo, RpcTestMempoolAcceptResult, RpcWalletInfo, RpcWalletCreateFundedPsbtResult, RpcWalletProcessPsbtResult, RpcTransaction, RpcWalletTransaction, RpcZmqNotification } from "./types.js";
|
|
2
2
|
interface RpcRequestPayload {
|
|
3
3
|
readonly body: string;
|
|
4
4
|
readonly headers: Record<string, string>;
|
|
@@ -63,6 +63,7 @@ export declare class BitcoinRpcClient {
|
|
|
63
63
|
getRawMempoolVerbose(): Promise<RpcRawMempoolVerbose>;
|
|
64
64
|
getMempoolInfo(): Promise<RpcMempoolInfo>;
|
|
65
65
|
getMempoolEntry(txid: string): Promise<RpcMempoolEntry>;
|
|
66
|
+
estimateSmartFee(confirmTarget: number, mode: "conservative" | "economical"): Promise<RpcEstimateSmartFeeResult>;
|
|
66
67
|
saveMempool(): Promise<null>;
|
|
67
68
|
getRawTransaction(txid: string, verbose?: boolean): Promise<RpcTransaction>;
|
|
68
69
|
getTransaction(walletName: string, txid: string): Promise<RpcWalletTransaction>;
|
package/dist/bitcoind/rpc.js
CHANGED
|
@@ -307,6 +307,9 @@ export class BitcoinRpcClient {
|
|
|
307
307
|
getMempoolEntry(txid) {
|
|
308
308
|
return this.call("getmempoolentry", [txid]);
|
|
309
309
|
}
|
|
310
|
+
estimateSmartFee(confirmTarget, mode) {
|
|
311
|
+
return this.call("estimatesmartfee", [confirmTarget, mode]);
|
|
312
|
+
}
|
|
310
313
|
saveMempool() {
|
|
311
314
|
return this.call("savemempool");
|
|
312
315
|
}
|