@midnight-ntwrk/wallet-sdk-capabilities 4.0.0-beta.2 → 4.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +27 -3
  2. package/dist/chainVersion/chainVersionProbe.d.ts +95 -0
  3. package/dist/chainVersion/chainVersionProbe.js +83 -0
  4. package/dist/chainVersion/index.d.ts +1 -0
  5. package/dist/chainVersion/index.js +13 -0
  6. package/dist/codecs/index.d.ts +1 -0
  7. package/dist/codecs/index.js +13 -0
  8. package/dist/codecs/ledgerParameters.d.ts +81 -0
  9. package/dist/codecs/ledgerParameters.js +63 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +3 -0
  12. package/dist/pendingTransactions/pendingTransactions.d.ts +113 -9
  13. package/dist/pendingTransactions/pendingTransactions.js +147 -30
  14. package/dist/pendingTransactions/pendingTransactionsService.d.ts +25 -9
  15. package/dist/pendingTransactions/pendingTransactionsService.js +33 -21
  16. package/dist/proving/index.d.ts +2 -0
  17. package/dist/proving/index.js +2 -0
  18. package/dist/proving/provingService.d.ts +175 -15
  19. package/dist/proving/provingService.js +104 -11
  20. package/dist/proving/v8ProvingService.d.ts +53 -0
  21. package/dist/proving/v8ProvingService.js +71 -0
  22. package/dist/proving/versionedProving.d.ts +42 -0
  23. package/dist/proving/versionedProving.js +103 -0
  24. package/dist/signatures/index.d.ts +2 -0
  25. package/dist/signatures/index.js +14 -0
  26. package/dist/signatures/signing.d.ts +37 -0
  27. package/dist/signatures/signing.js +13 -0
  28. package/dist/signatures/v8Signatures.d.ts +54 -0
  29. package/dist/signatures/v8Signatures.js +62 -0
  30. package/dist/simulation/ForkSimulator.d.ts +114 -0
  31. package/dist/simulation/ForkSimulator.js +209 -0
  32. package/dist/simulation/LedgerTranslation.d.ts +53 -0
  33. package/dist/simulation/LedgerTranslation.js +56 -0
  34. package/dist/simulation/core/VersionTimeline.d.ts +54 -0
  35. package/dist/simulation/core/VersionTimeline.js +56 -0
  36. package/dist/simulation/core/blocks.d.ts +38 -0
  37. package/dist/simulation/core/blocks.js +52 -0
  38. package/dist/simulation/core/index.d.ts +13 -0
  39. package/dist/simulation/core/index.js +25 -0
  40. package/dist/simulation/core/strictness.d.ts +29 -0
  41. package/dist/simulation/core/strictness.js +39 -0
  42. package/dist/simulation/index.d.ts +22 -2
  43. package/dist/simulation/index.js +25 -14
  44. package/dist/simulation/v8/Simulator.d.ts +231 -0
  45. package/dist/simulation/v8/Simulator.js +503 -0
  46. package/dist/simulation/{SimulatorState.d.ts → v8/SimulatorState.d.ts} +31 -44
  47. package/dist/simulation/v8/SimulatorState.js +290 -0
  48. package/dist/simulation/v8/index.d.ts +2 -0
  49. package/dist/simulation/v8/index.js +26 -0
  50. package/dist/simulation/{Simulator.d.ts → v9/Simulator.d.ts} +57 -6
  51. package/dist/simulation/{Simulator.js → v9/Simulator.js} +68 -18
  52. package/dist/simulation/v9/SimulatorState.d.ts +336 -0
  53. package/dist/simulation/{SimulatorState.js → v9/SimulatorState.js} +34 -67
  54. package/dist/simulation/v9/index.d.ts +2 -0
  55. package/dist/simulation/v9/index.js +26 -0
  56. package/dist/submission/submissionService.d.ts +2 -1
  57. package/dist/submission/submissionService.js +1 -1
  58. package/dist/validation/blockData.d.ts +42 -2
  59. package/dist/validation/blockData.js +55 -8
  60. package/dist/validation/index.d.ts +2 -0
  61. package/dist/validation/index.js +2 -0
  62. package/dist/validation/v8ValidationService.d.ts +29 -0
  63. package/dist/validation/v8ValidationService.js +48 -0
  64. package/dist/validation/validationService.d.ts +132 -17
  65. package/dist/validation/validationService.js +88 -42
  66. package/dist/validation/versionedValidation.d.ts +46 -0
  67. package/dist/validation/versionedValidation.js +55 -0
  68. package/package.json +23 -9
@@ -0,0 +1,290 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ /**
14
+ * SimulatorState types and pure functions for state manipulation.
15
+ *
16
+ * This module contains the core data types and pure functions for working with simulator state. All functions are
17
+ * synchronous and side-effect free.
18
+ */
19
+ import { Either, Function as EFunction, Array as EArray } from 'effect';
20
+ import { LedgerState, WellFormedStrictness, TransactionContext, } from '@midnight-ntwrk/ledger-v8';
21
+ import { DateOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
22
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
23
+ import { blockHash, protocolVersionAt, } from '../core/index.js';
24
+ // Version-agnostic simulator internals, re-exported so each version's barrel presents one complete surface.
25
+ export { blockHash, defaultStrictness, genesisStrictness, getProtocolVersion, nextBlockContextFromBlock, protocolVersionAt, scheduleFork, setProtocolVersion, } from '../core/index.js';
26
+ /**
27
+ * Assign strictness to a pending transaction, creating a ready transaction. If the pending transaction already has
28
+ * strictness, use it; otherwise use the provided default.
29
+ */
30
+ export const assignStrictness = (pendingTx, defaultStrictness) => ({
31
+ tx: pendingTx.tx,
32
+ strictness: pendingTx.strictness ?? defaultStrictness,
33
+ });
34
+ /**
35
+ * Assign strictness to all pending transactions, creating ready transactions. Transactions with existing strictness
36
+ * keep their strictness; others get the default.
37
+ */
38
+ export const assignStrictnessToAll = (transactions, defaultStrictness) => transactions.map((tx) => assignStrictness(tx, defaultStrictness));
39
+ // =============================================================================
40
+ // State Accessors (Pure Functions)
41
+ // =============================================================================
42
+ /** Get the last produced block, or undefined if no blocks yet. */
43
+ export const getLastBlock = (state) => EArray.lastNonEmpty(state.blocks);
44
+ /** Get the current block number (height of the last block, or 0 if no blocks). */
45
+ export const getCurrentBlockNumber = (state) => getLastBlock(state)?.number;
46
+ /** Get a block by its number. */
47
+ export const getBlockByNumber = EFunction.dual(2, (state, number) => state.blocks.find((b) => b.number === number));
48
+ /** Get all transaction results from the last block. */
49
+ export const getLastBlockResults = (state) => getLastBlock(state)?.transactions.map((t) => t.result) ?? [];
50
+ /** Get all events from the last block (flattened from all transactions). */
51
+ export const getLastBlockEvents = (state) => getLastBlockResults(state).flatMap((r) => r.events);
52
+ /**
53
+ * Get all events from blocks with number >= fromBlockNumber. Returns events ordered by block number, with each block's
54
+ * transactions flattened.
55
+ *
56
+ * Use this with the wallet's next-to-process index: `appliedIndex` after processing should be set to `lastBlockNumber +
57
+ * 1`, not `lastBlockNumber`.
58
+ */
59
+ export const getBlockEventsFrom = EFunction.dual(2, (state, fromBlockNumber) => state.blocks
60
+ .filter((b) => b.number >= fromBlockNumber)
61
+ .flatMap((b) => b.transactions.flatMap((t) => t.result.events)));
62
+ /**
63
+ * @deprecated Use getBlockEventsFrom instead with proper appliedIndex semantics Get all events from blocks with number
64
+ *
65
+ * > AfterBlockNumber.
66
+ */
67
+ export const getBlockEventsSince = EFunction.dual(2, (state, afterBlockNumber) => state.blocks
68
+ .filter((b) => b.number > afterBlockNumber)
69
+ .flatMap((b) => b.transactions.flatMap((t) => t.result.events)));
70
+ /** Check if there are pending transactions in the mempool. */
71
+ export const hasPendingTransactions = (state) => state.mempool.length > 0;
72
+ /** Get the current simulator time. */
73
+ export const getCurrentTime = (state) => state.currentTime;
74
+ // =============================================================================
75
+ // State Transformations (Pure Functions)
76
+ // =============================================================================
77
+ /** Resolve fullness from spec and state. */
78
+ export const resolveFullness = (spec, state) => typeof spec === 'function' ? spec(state) : spec;
79
+ /** Create a block production request that includes all mempool transactions. */
80
+ export const allMempoolTransactions = (state, fullness, defaultStrictness) => ({
81
+ transactions: assignStrictnessToAll(state.mempool, defaultStrictness),
82
+ fullness,
83
+ });
84
+ /**
85
+ * Create a blank initial state.
86
+ *
87
+ * @param networkId - Network identifier
88
+ * @param options - Optional genesis parameters
89
+ * @param options.protocolVersion - Version the chain starts on (defaults to the minimum supported version)
90
+ * @param options.genesisBlockNumber - Height of the genesis block (defaults to 0; a ledger-v9 chain continues numbering
91
+ * from the fork height instead)
92
+ * @param options.genesisTime - Timestamp of the genesis block (defaults to the epoch)
93
+ */
94
+ export const blankState = async (networkId, options) => {
95
+ const protocolVersion = options?.protocolVersion ?? ProtocolVersion.MinSupportedVersion;
96
+ const genesisBlockNumber = options?.genesisBlockNumber ?? 0n;
97
+ const genesisTime = options?.genesisTime ?? new Date(0);
98
+ const blankGenesis = {
99
+ number: genesisBlockNumber,
100
+ hash: await blockHash(genesisBlockNumber),
101
+ timestamp: genesisTime,
102
+ transactions: [],
103
+ protocolVersion,
104
+ };
105
+ return {
106
+ networkId,
107
+ ledger: LedgerState.blank(networkId),
108
+ blocks: [blankGenesis],
109
+ mempool: [],
110
+ currentTime: genesisTime,
111
+ protocolVersion,
112
+ scheduledForks: [],
113
+ };
114
+ };
115
+ /** Add a pending transaction to the mempool. */
116
+ export const addToMempool = (state, pendingTx) => ({
117
+ ...state,
118
+ mempool: [...state.mempool, pendingTx],
119
+ });
120
+ /** Remove transactions from the mempool. */
121
+ export const removeFromMempool = (state, transactions) => {
122
+ const txsToRemove = new Set(transactions.map((t) => t.tx));
123
+ return {
124
+ ...state,
125
+ mempool: state.mempool.filter((pending) => !txsToRemove.has(pending.tx)),
126
+ };
127
+ };
128
+ /** Advance the simulator time by the given number of seconds. */
129
+ export const advanceTime = (state, seconds) => ({
130
+ ...state,
131
+ currentTime: DateOps.addSeconds(state.currentTime, seconds),
132
+ });
133
+ /** Update the ledger state. */
134
+ export const updateLedger = (state, ledger) => ({
135
+ ...state,
136
+ ledger,
137
+ });
138
+ /** Append a block to the state and update time. */
139
+ export const appendBlock = (state, block, newLedger) => ({
140
+ ...state,
141
+ ledger: newLedger,
142
+ blocks: [...state.blocks, block],
143
+ currentTime: block.timestamp,
144
+ });
145
+ // =============================================================================
146
+ // Transaction Application (Pure Function)
147
+ // =============================================================================
148
+ /**
149
+ * Pure state transition: apply a transaction to the simulator state. Returns Either with the new state or an error.
150
+ *
151
+ * @param state - Current simulator state
152
+ * @param tx - Transaction to apply
153
+ * @param strictness - Well-formedness strictness options
154
+ * @param blockContext - Block context for the transaction
155
+ * @param options - Optional parameters
156
+ * @param options.blockNumber - Override block number (defaults to last block + 1)
157
+ * @param options.blockFullness - Override detailed block fullness (SyntheticCost)
158
+ * @param options.overallBlockFullness - Override overall block fullness (0-1 value)
159
+ */
160
+ export const applyTransaction = (state, tx, strictness, blockContext, options) => {
161
+ return LedgerOps.ledgerTry(() => {
162
+ const computedFullness = options?.blockFullness ?? tx.cost(state.ledger.parameters);
163
+ const detailedBlockFullness = state.ledger.parameters.normalizeFullness(computedFullness);
164
+ const computedBlockFullness = options?.overallBlockFullness ??
165
+ Math.max(detailedBlockFullness.readTime, detailedBlockFullness.computeTime, detailedBlockFullness.blockUsage, detailedBlockFullness.bytesWritten, detailedBlockFullness.bytesChurned);
166
+ const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(state) + 1n;
167
+ const protocolVersion = protocolVersionAt(state, blockNumber);
168
+ const blockTime = state.currentTime;
169
+ const verifiedTransaction = tx.wellFormed(state.ledger, strictness, blockTime);
170
+ const transactionContext = new TransactionContext(state.ledger, blockContext);
171
+ const [newLedgerState, txResult] = state.ledger.apply(verifiedTransaction, transactionContext);
172
+ const newBlock = {
173
+ number: blockNumber,
174
+ hash: blockContext.parentBlockHash,
175
+ timestamp: blockTime,
176
+ transactions: [{ tx, result: txResult }],
177
+ protocolVersion,
178
+ };
179
+ const newState = {
180
+ ...state,
181
+ ledger: newLedgerState.postBlockUpdate(blockTime, detailedBlockFullness, computedBlockFullness),
182
+ blocks: [...state.blocks, newBlock],
183
+ protocolVersion,
184
+ };
185
+ return [newBlock, newState];
186
+ });
187
+ };
188
+ /**
189
+ * Process a single pending transaction against the latest ledger state. Returns Either with the processing result or an
190
+ * error.
191
+ *
192
+ * @param ledger - The ledger state to process against
193
+ * @param readyTx - Transaction to process
194
+ * @param blockTime - Block timestamp
195
+ * @param blockContext - Block context
196
+ * @param minFullness - Minimum block fullness to use
197
+ */
198
+ export const processTransaction = (ledger, readyTx, blockTime, blockContext, minFullness) => {
199
+ return LedgerOps.ledgerTry(() => {
200
+ const computedFullness = readyTx.tx.cost(ledger.parameters);
201
+ const detailedBlockFullness = ledger.parameters.normalizeFullness(computedFullness);
202
+ const computedBlockFullness = Math.max(minFullness, detailedBlockFullness.readTime, detailedBlockFullness.computeTime, detailedBlockFullness.blockUsage, detailedBlockFullness.bytesWritten, detailedBlockFullness.bytesChurned);
203
+ // Use the assigned strictness
204
+ const verifiedTransaction = readyTx.tx.wellFormed(ledger, readyTx.strictness, blockTime);
205
+ const transactionContext = new TransactionContext(ledger, blockContext);
206
+ const [newLedgerState, txResult] = ledger.apply(verifiedTransaction, transactionContext);
207
+ const postBlockLedger = newLedgerState.postBlockUpdate(blockTime, detailedBlockFullness, computedBlockFullness);
208
+ return {
209
+ tx: readyTx.tx,
210
+ result: txResult,
211
+ newLedger: postBlockLedger,
212
+ };
213
+ });
214
+ };
215
+ /**
216
+ * Process multiple transactions in sequence, accumulating results. Returns Either with all results and final ledger, or
217
+ * first error. Each transaction uses its assigned strictness (from ReadyTransaction).
218
+ *
219
+ * @param ledger - Initial ledger state
220
+ * @param transactions - Transactions to process (each with assigned strictness)
221
+ * @param blockTime - Block timestamp
222
+ * @param blockContext - Block context
223
+ * @param fullness - Block fullness (0-1)
224
+ */
225
+ export const processTransactions = (ledger, transactions, blockTime, blockContext, fullness) => transactions.reduce((acc, readyTx) => Either.flatMap(acc, ({ blockTransactions, finalLedger }) => Either.map(processTransaction(finalLedger, readyTx, blockTime, blockContext, fullness), (result) => ({
226
+ blockTransactions: [...blockTransactions, { tx: result.tx, result: result.result }],
227
+ finalLedger: result.newLedger,
228
+ }))), Either.right({ blockTransactions: [], finalLedger: ledger }));
229
+ /**
230
+ * Create a block from processed transactions and update state. Pure function that takes pre-computed block hash.
231
+ *
232
+ * @param state - Current simulator state
233
+ * @param blockTransactions - Processed transactions to include
234
+ * @param blockHash - Pre-computed block hash
235
+ * @param blockTime - Block timestamp
236
+ * @param newLedger - New ledger state after processing transactions
237
+ * @param processedTxs - Ready transactions to remove from mempool
238
+ */
239
+ export const createBlock = (state, blockTransactions, blockHashValue, blockTime, newLedger, processedTxs) => {
240
+ const nextBlockNumber = getCurrentBlockNumber(state) + 1n;
241
+ // Resolved once, at production time: the block and the chain's current version cannot disagree.
242
+ const protocolVersion = protocolVersionAt(state, nextBlockNumber);
243
+ const block = {
244
+ number: nextBlockNumber,
245
+ hash: blockHashValue,
246
+ timestamp: blockTime,
247
+ transactions: blockTransactions,
248
+ protocolVersion,
249
+ };
250
+ const txsToRemove = new Set(processedTxs.map((t) => t.tx));
251
+ const newState = {
252
+ ...state,
253
+ ledger: newLedger,
254
+ blocks: [...state.blocks, block],
255
+ mempool: state.mempool.filter((pending) => !txsToRemove.has(pending.tx)),
256
+ currentTime: blockTime,
257
+ protocolVersion,
258
+ };
259
+ return [block, newState];
260
+ };
261
+ /**
262
+ * Create an empty block (no transactions) and update state.
263
+ *
264
+ * @param state - Current simulator state
265
+ * @param blockHashValue - Pre-computed block hash
266
+ * @param blockTime - Block timestamp
267
+ * @param processedTxs - Original pending transactions to remove from mempool (if any)
268
+ */
269
+ export const createEmptyBlock = (state, blockHashValue, blockTime, processedTxs = []) => {
270
+ return createBlock(state, [], blockHashValue, blockTime, state.ledger, processedTxs);
271
+ };
272
+ // =============================================================================
273
+ // Helper Functions
274
+ // =============================================================================
275
+ /**
276
+ * Create a WellFormedStrictness instance with configurable options. All options default to false for maximum testing
277
+ * flexibility.
278
+ *
279
+ * Note: WellFormedStrictness is a class from the ledger library that requires mutation to configure. This is
280
+ * unavoidable given the external API design.
281
+ */
282
+ export const createStrictness = (config = {}) => {
283
+ const strictness = new WellFormedStrictness();
284
+ strictness.enforceBalancing = config.enforceBalancing ?? false;
285
+ strictness.verifyNativeProofs = config.verifyNativeProofs ?? false;
286
+ strictness.verifyContractProofs = config.verifyContractProofs ?? false;
287
+ strictness.enforceLimits = config.enforceLimits ?? false;
288
+ strictness.verifySignatures = config.verifySignatures ?? false;
289
+ return strictness;
290
+ };
@@ -0,0 +1,2 @@
1
+ export { getLastBlock, getCurrentBlockNumber, getCurrentTime, getProtocolVersion, protocolVersionAt, getBlockByNumber, getLastBlockResults, getLastBlockEvents, getBlockEventsFrom, getBlockEventsSince, hasPendingTransactions, resolveFullness, allMempoolTransactions, blankState, addToMempool, removeFromMempool, advanceTime, updateLedger, appendBlock, applyTransaction, setProtocolVersion, scheduleFork, processTransaction, processTransactions, createBlock, createEmptyBlock, type TransactionProcessingResult, createStrictness, blockHash, assignStrictness, assignStrictnessToAll, defaultStrictness, genesisStrictness, type SimulatorState, type Block, type BlockTransaction, type BlockInfo, type PendingTransaction, type ReadyTransaction, type BlockProductionRequest, type BlockProducer, type FullnessSpec, type GenesisMint, type ScheduledFork, type StrictnessConfig, } from './SimulatorState.js';
2
+ export { Simulator, immediateBlockProducer, type SimulatorConfig } from './Simulator.js';
@@ -0,0 +1,26 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ // Re-export everything from SimulatorState
14
+ export {
15
+ // State accessor functions (composable with simulator.query())
16
+ getLastBlock, getCurrentBlockNumber, getCurrentTime, getProtocolVersion, protocolVersionAt, getBlockByNumber, getLastBlockResults, getLastBlockEvents, getBlockEventsFrom, getBlockEventsSince, hasPendingTransactions,
17
+ // State transformation functions
18
+ resolveFullness, allMempoolTransactions, blankState, addToMempool, removeFromMempool, advanceTime, updateLedger, appendBlock, applyTransaction, setProtocolVersion, scheduleFork,
19
+ // Block production functions
20
+ processTransaction, processTransactions, createBlock, createEmptyBlock,
21
+ // Helper functions
22
+ createStrictness, blockHash, assignStrictness, assignStrictnessToAll,
23
+ // Strictness constants
24
+ defaultStrictness, genesisStrictness, } from './SimulatorState.js';
25
+ // Re-export from Simulator
26
+ export { Simulator, immediateBlockProducer } from './Simulator.js';
@@ -12,10 +12,10 @@
12
12
  import { type Array as Arr, Effect, type Scope, Stream, SubscriptionRef } from 'effect';
13
13
  import { type ProofErasedTransaction, type SignatureVerifyingKey } from '@midnightntwrk/ledger-v9';
14
14
  import { LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
15
- import { NetworkId } from '@midnight-ntwrk/wallet-sdk-abstractions';
15
+ import { NetworkId, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
16
16
  import { type Block, type BlockProducer, type FullnessSpec, type GenesisMint, type SimulatorState, type StrictnessConfig } from './SimulatorState.js';
17
- export type { SimulatorState, Block, BlockTransaction, PendingTransaction, BlockInfo, BlockProductionRequest, BlockProducer, FullnessSpec, GenesisMint, StrictnessConfig, } from './SimulatorState.js';
18
- export { getLastBlock, getCurrentBlockNumber, getBlockByNumber, getLastBlockResults, getLastBlockEvents, hasPendingTransactions, getCurrentTime, applyTransaction, defaultStrictness, genesisStrictness, createStrictness, } from './SimulatorState.js';
17
+ export type { SimulatorState, Block, BlockTransaction, PendingTransaction, BlockInfo, BlockProductionRequest, BlockProducer, FullnessSpec, GenesisMint, ScheduledFork, StrictnessConfig, } from './SimulatorState.js';
18
+ export { getLastBlock, getCurrentBlockNumber, getBlockByNumber, getLastBlockResults, getLastBlockEvents, hasPendingTransactions, getCurrentTime, getProtocolVersion, protocolVersionAt, applyTransaction, defaultStrictness, genesisStrictness, createStrictness, } from './SimulatorState.js';
19
19
  /**
20
20
  * Default block producer: produces a block for each state change with non-empty mempool.
21
21
  *
@@ -37,6 +37,25 @@ export type SimulatorConfig = Readonly<{
37
37
  networkId?: NetworkId.NetworkId;
38
38
  /** Custom block producer. Defaults to immediateBlockProducer(). */
39
39
  blockProducer?: BlockProducer;
40
+ /**
41
+ * Protocol version the chain starts on. Defaults to {@link ProtocolVersion.MinSupportedVersion}.
42
+ *
43
+ * The version a fork activates is never built in — schedule it with {@link Simulator.scheduleFork} or switch to it
44
+ * with {@link Simulator.setProtocolVersion}.
45
+ */
46
+ protocolVersion?: ProtocolVersion.ProtocolVersion;
47
+ /**
48
+ * Height of the genesis block. Defaults to 0.
49
+ *
50
+ * Set this to continue an existing chain's numbering, as a ledger-v9 chain does from the fork height.
51
+ */
52
+ genesisBlockNumber?: bigint;
53
+ /**
54
+ * Timestamp of the genesis block, from which simulator time advances. Defaults to the epoch.
55
+ *
56
+ * Set this to continue an existing chain's timeline, so that time-sensitive behaviour does not restart at the epoch.
57
+ */
58
+ genesisTime?: Date;
40
59
  }>;
41
60
  /**
42
61
  * Unified simulator for wallet testing.
@@ -85,11 +104,19 @@ export declare class Simulator {
85
104
  private static initBlank;
86
105
  /**
87
106
  * Initialize simulator with genesis mints (pre-funded accounts). Supports shielded and unshielded token mints. Night
88
- * tokens are auto-detected by comparing tokenType with nativeToken().raw.
107
+ * tokens are auto-detected by comparing tokenType with `Token.night`.
89
108
  */
90
109
  private static initWithGenesis;
91
- /** Create a Simulator from an initial state with proper stream setup. */
92
- private static fromState;
110
+ /**
111
+ * Create a Simulator from an initial state with proper stream setup.
112
+ *
113
+ * The low-level constructor behind {@link Simulator.init}. Use it when the initial state cannot be expressed as a
114
+ * config — most importantly a ledger-v9 chain, whose ledger is handed over from the chain before it.
115
+ *
116
+ * @param initialState - State the simulator starts from
117
+ * @param blockProducer - Custom block producer (defaults to immediateBlockProducer())
118
+ */
119
+ static fromState(initialState: SimulatorState, blockProducer?: BlockProducer): Effect.Effect<Simulator, never, Scope.Scope>;
93
120
  /** Observable stream of simulator state changes. */
94
121
  readonly state$: Stream.Stream<SimulatorState>;
95
122
  constructor(stateRef: SubscriptionRef.SubscriptionRef<SimulatorState>, state$: Stream.Stream<SimulatorState>);
@@ -141,6 +168,30 @@ export declare class Simulator {
141
168
  submitAndForget(tx: ProofErasedTransaction, options?: {
142
169
  strictness?: StrictnessConfig;
143
170
  }): Effect.Effect<void>;
171
+ /**
172
+ * Produce a block with no transactions, advancing the chain by one height.
173
+ *
174
+ * Bypasses the configured block producer, which only produces when there is something in the mempool. Use it to reach
175
+ * a given height without traffic — a fork happens at a height whether or not transactions were submitted.
176
+ *
177
+ * @returns The produced block
178
+ */
179
+ produceEmptyBlock(): Effect.Effect<Block, LedgerOps.LedgerError>;
180
+ /**
181
+ * Switch the chain to a protocol version, effective for blocks produced from now on. Already-produced blocks keep the
182
+ * version they were produced under.
183
+ *
184
+ * @param version - Version to switch the chain to
185
+ */
186
+ setProtocolVersion(version: ProtocolVersion.ProtocolVersion): Effect.Effect<void>;
187
+ /**
188
+ * Schedule a protocol version to activate at a block height — a fork on the simulated chain. The block produced at
189
+ * that height, and every block after it, is stamped with `version`.
190
+ *
191
+ * @param atBlock - Height of the first block produced under `version`
192
+ * @param version - Version that activates at `atBlock`
193
+ */
194
+ scheduleFork(atBlock: bigint, version: ProtocolVersion.ProtocolVersion): Effect.Effect<void>;
144
195
  /**
145
196
  * Fast-forward the simulator time by the given number of seconds. Does not produce a block - only advances the
146
197
  * internal clock. Useful for testing time-sensitive functionality like TTL.
@@ -22,10 +22,10 @@
22
22
  * - Time advancement for TTL and time-sensitive tests
23
23
  */
24
24
  import { Effect, Either, pipe, Stream, SubscriptionRef } from 'effect';
25
- import { addressFromKey, ClaimRewardsTransaction, createShieldedCoinInfo, Intent, LedgerState, nativeToken, SignatureErased, Transaction, TransactionContext, UnshieldedOffer, ZswapOffer, ZswapOutput, } from '@midnightntwrk/ledger-v9';
25
+ import { addressFromKey, ClaimRewardsTransaction, createShieldedCoinInfo, Intent, LedgerState, SignatureErased, Transaction, TransactionContext, UnshieldedOffer, ZswapOffer, ZswapOutput, } from '@midnightntwrk/ledger-v9';
26
26
  import { DateOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
27
- import { NetworkId } from '@midnight-ntwrk/wallet-sdk-abstractions';
28
- import { addToMempool, allMempoolTransactions, blankState, blockHash, createBlock, createEmptyBlock, createStrictness, defaultStrictness, genesisStrictness, getLastBlock, hasPendingTransactions, nextBlockContextFromBlock, processTransactions, removeFromMempool, resolveFullness, } from './SimulatorState.js';
27
+ import { NetworkId, ProtocolVersion, Token } from '@midnight-ntwrk/wallet-sdk-abstractions';
28
+ import { addToMempool, allMempoolTransactions, blankState, blockHash, createBlock, createEmptyBlock, createStrictness, defaultStrictness, genesisStrictness, getLastBlock, hasPendingTransactions, nextBlockContextFromBlock, processTransactions, removeFromMempool, resolveFullness, scheduleFork, setProtocolVersion, } from './SimulatorState.js';
29
29
  // =============================================================================
30
30
  // Type Guards and Helpers for Genesis Mints
31
31
  // =============================================================================
@@ -34,12 +34,12 @@ const isShieldedMint = (mint) => mint.type === 'shielded';
34
34
  /** Type guard for unshielded genesis mint. */
35
35
  const isUnshieldedMint = (mint) => mint.type === 'unshielded';
36
36
  /**
37
- * Check if an unshielded mint is for the native Night token. Night is auto-detected by comparing tokenType with
38
- * ledger.nativeToken().raw.
37
+ * Check if an unshielded mint is for the native Night token. Night is auto-detected by comparing tokenType with the
38
+ * SDK's own `Token.night`, which is what both ledger versions call the native token.
39
39
  */
40
- const isNightToken = (tokenType) => tokenType === nativeToken().raw;
40
+ const isNightToken = (tokenType) => tokenType === Token.night;
41
41
  // Re-export state accessors for backward compatibility
42
- export { getLastBlock, getCurrentBlockNumber, getBlockByNumber, getLastBlockResults, getLastBlockEvents, hasPendingTransactions, getCurrentTime, applyTransaction, defaultStrictness, genesisStrictness, createStrictness, } from './SimulatorState.js';
42
+ export { getLastBlock, getCurrentBlockNumber, getBlockByNumber, getLastBlockResults, getLastBlockEvents, hasPendingTransactions, getCurrentTime, getProtocolVersion, protocolVersionAt, applyTransaction, defaultStrictness, genesisStrictness, createStrictness, } from './SimulatorState.js';
43
43
  // =============================================================================
44
44
  // Block Producers
45
45
  // =============================================================================
@@ -102,19 +102,28 @@ export class Simulator {
102
102
  */
103
103
  static init(config = {}) {
104
104
  const networkId = config.networkId ?? NetworkId.NetworkId.Undeployed;
105
+ const genesis = {
106
+ protocolVersion: config.protocolVersion ?? ProtocolVersion.MinSupportedVersion,
107
+ blockNumber: config.genesisBlockNumber ?? 0n,
108
+ time: config.genesisTime ?? new Date(0),
109
+ };
105
110
  return config.genesisMints !== undefined && config.genesisMints.length > 0
106
- ? Simulator.initWithGenesis(config.genesisMints, networkId, config.blockProducer)
107
- : Simulator.initBlank(networkId, config.blockProducer);
111
+ ? Simulator.initWithGenesis(config.genesisMints, networkId, genesis, config.blockProducer)
112
+ : Simulator.initBlank(networkId, genesis, config.blockProducer);
108
113
  }
109
114
  /** Initialize simulator with blank ledger state. */
110
- static initBlank(networkId, blockProducer) {
111
- return pipe(Effect.promise(() => blankState(networkId)), Effect.flatMap((state) => Simulator.fromState(state, blockProducer)));
115
+ static initBlank(networkId, genesis, blockProducer) {
116
+ return pipe(Effect.promise(() => blankState(networkId, {
117
+ protocolVersion: genesis.protocolVersion,
118
+ genesisBlockNumber: genesis.blockNumber,
119
+ genesisTime: genesis.time,
120
+ })), Effect.flatMap((state) => Simulator.fromState(state, blockProducer)));
112
121
  }
113
122
  /**
114
123
  * Initialize simulator with genesis mints (pre-funded accounts). Supports shielded and unshielded token mints. Night
115
- * tokens are auto-detected by comparing tokenType with nativeToken().raw.
124
+ * tokens are auto-detected by comparing tokenType with `Token.night`.
116
125
  */
117
- static initWithGenesis(genesisMints, networkId = NetworkId.NetworkId.Undeployed, blockProducer) {
126
+ static initWithGenesis(genesisMints, networkId = NetworkId.NetworkId.Undeployed, genesis, blockProducer) {
118
127
  const noStrictness = createStrictness();
119
128
  // Pure function to extract shielded mint data (returns array for flatMap)
120
129
  const toShieldedMint = (mint) => isShieldedMint(mint) ? [{ tokenType: mint.tokenType, amount: mint.amount, keys: mint.recipient }] : [];
@@ -124,7 +133,7 @@ export class Simulator {
124
133
  ? [{ tokenType: mint.tokenType, amount: mint.amount, recipient: mint.recipient }]
125
134
  : [];
126
135
  // Pure function to extract Night mint data from unshielded mints (returns array for flatMap)
127
- // Night is auto-detected by comparing tokenType with nativeToken().raw
136
+ // Night is auto-detected by comparing tokenType with `Token.night`
128
137
  const toNightMint = (mint) => isUnshieldedMint(mint) && isNightToken(mint.tokenType) && mint.verifyingKey !== undefined
129
138
  ? [{ amount: mint.amount, recipient: mint.recipient, verifyingKey: mint.verifyingKey }]
130
139
  : [];
@@ -185,9 +194,9 @@ export class Simulator {
185
194
  return { ledgerState: newState, tx: proofErasedTx, result };
186
195
  };
187
196
  return Effect.gen(function* () {
188
- const genesisTime = new Date(0);
197
+ const genesisTime = genesis.time;
189
198
  const emptyState = LedgerState.blank(networkId);
190
- const context = yield* Effect.promise(() => nextBlockContextFromBlock(undefined, genesisTime));
199
+ const context = yield* Effect.promise(() => nextBlockContextFromBlock(undefined, genesisTime, genesis.blockNumber));
191
200
  // Process shielded and unshielded mints first
192
201
  const init = makeInitialTransactions(emptyState, context, genesisTime);
193
202
  // Apply post-block update before processing Night mints
@@ -211,10 +220,11 @@ export class Simulator {
211
220
  const allTransactions = [...init.transactions, ...nightResults.transactions];
212
221
  // Create genesis block with all transactions
213
222
  const genesisBlock = {
214
- number: 0n,
223
+ number: genesis.blockNumber,
215
224
  hash: context.parentBlockHash,
216
225
  timestamp: genesisTime,
217
226
  transactions: allTransactions,
227
+ protocolVersion: genesis.protocolVersion,
218
228
  };
219
229
  const initialState = {
220
230
  networkId,
@@ -222,11 +232,21 @@ export class Simulator {
222
232
  blocks: [genesisBlock],
223
233
  mempool: [],
224
234
  currentTime: genesisTime, // Time stays at genesis; next block will advance it
235
+ protocolVersion: genesis.protocolVersion,
236
+ scheduledForks: [],
225
237
  };
226
238
  return yield* Simulator.fromState(initialState, blockProducer);
227
239
  });
228
240
  }
229
- /** Create a Simulator from an initial state with proper stream setup. */
241
+ /**
242
+ * Create a Simulator from an initial state with proper stream setup.
243
+ *
244
+ * The low-level constructor behind {@link Simulator.init}. Use it when the initial state cannot be expressed as a
245
+ * config — most importantly a ledger-v9 chain, whose ledger is handed over from the chain before it.
246
+ *
247
+ * @param initialState - State the simulator starts from
248
+ * @param blockProducer - Custom block producer (defaults to immediateBlockProducer())
249
+ */
230
250
  static fromState(initialState, blockProducer) {
231
251
  return Effect.gen(function* () {
232
252
  const stateRef = yield* SubscriptionRef.make(initialState);
@@ -353,6 +373,36 @@ export class Simulator {
353
373
  const pendingTx = options?.strictness !== undefined ? { tx, strictness: createStrictness(options.strictness) } : { tx };
354
374
  return SubscriptionRef.update(this.#stateRef, (s) => addToMempool(s, pendingTx));
355
375
  }
376
+ /**
377
+ * Produce a block with no transactions, advancing the chain by one height.
378
+ *
379
+ * Bypasses the configured block producer, which only produces when there is something in the mempool. Use it to reach
380
+ * a given height without traffic — a fork happens at a height whether or not transactions were submitted.
381
+ *
382
+ * @returns The produced block
383
+ */
384
+ produceEmptyBlock() {
385
+ return this.#produceBlock({ transactions: [], fullness: 0 });
386
+ }
387
+ /**
388
+ * Switch the chain to a protocol version, effective for blocks produced from now on. Already-produced blocks keep the
389
+ * version they were produced under.
390
+ *
391
+ * @param version - Version to switch the chain to
392
+ */
393
+ setProtocolVersion(version) {
394
+ return SubscriptionRef.update(this.#stateRef, (state) => setProtocolVersion(state, version));
395
+ }
396
+ /**
397
+ * Schedule a protocol version to activate at a block height — a fork on the simulated chain. The block produced at
398
+ * that height, and every block after it, is stamped with `version`.
399
+ *
400
+ * @param atBlock - Height of the first block produced under `version`
401
+ * @param version - Version that activates at `atBlock`
402
+ */
403
+ scheduleFork(atBlock, version) {
404
+ return SubscriptionRef.update(this.#stateRef, (state) => scheduleFork(state, atBlock, version));
405
+ }
356
406
  /**
357
407
  * Fast-forward the simulator time by the given number of seconds. Does not produce a block - only advances the
358
408
  * internal clock. Useful for testing time-sensitive functionality like TTL.