@midnight-ntwrk/wallet-sdk-capabilities 3.2.0 → 3.3.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.
@@ -0,0 +1,349 @@
1
+ /**
2
+ * SimulatorState types and pure functions for state manipulation.
3
+ *
4
+ * This module contains the core data types and pure functions for working with simulator state. All functions are
5
+ * synchronous and side-effect free.
6
+ */
7
+ import { Either, type Stream, Array as EArray } from 'effect';
8
+ import { LedgerState, type BlockContext, WellFormedStrictness, type TransactionResult, type ProofErasedTransaction, type SyntheticCost, type RawTokenType, type ZswapSecretKeys, type UserAddress, type SignatureVerifyingKey } from '@midnight-ntwrk/ledger-v8';
9
+ import { LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
10
+ import { type NetworkId } from '@midnight-ntwrk/wallet-sdk-abstractions';
11
+ /** A transaction included in a block with its execution result. */
12
+ export type BlockTransaction = Readonly<{
13
+ /** The transaction that was executed */
14
+ tx: ProofErasedTransaction;
15
+ /** The result of executing the transaction */
16
+ result: TransactionResult;
17
+ }>;
18
+ /** A produced block containing transactions and metadata. */
19
+ export type Block = Readonly<{
20
+ /** Block number (height) */
21
+ number: bigint;
22
+ /** Block hash */
23
+ hash: string;
24
+ /** Block timestamp */
25
+ timestamp: Date;
26
+ /** Transactions in this block, ordered by execution */
27
+ transactions: readonly BlockTransaction[];
28
+ }>;
29
+ /**
30
+ * Pending transaction waiting for block production. Strictness is optional - if not specified, block producer assigns
31
+ * default when creating block.
32
+ */
33
+ export type PendingTransaction = Readonly<{
34
+ tx: ProofErasedTransaction;
35
+ /** Optional per-transaction strictness. If not specified, block producer assigns default. */
36
+ strictness?: WellFormedStrictness;
37
+ }>;
38
+ /**
39
+ * Transaction ready for block production with strictness assigned. Block producer ensures all transactions have
40
+ * strictness before block creation.
41
+ */
42
+ export type ReadyTransaction = Readonly<{
43
+ tx: ProofErasedTransaction;
44
+ /** Strictness for validation (assigned by block producer if not specified on pending tx). */
45
+ strictness: WellFormedStrictness;
46
+ }>;
47
+ /** Simulator state containing the ledger, block history, and pending mempool. */
48
+ export type SimulatorState = Readonly<{
49
+ networkId: NetworkId.NetworkId;
50
+ ledger: LedgerState;
51
+ /** All produced blocks, ordered by block number */
52
+ blocks: EArray.NonEmptyArray<Block>;
53
+ /** Pending transactions waiting for block production */
54
+ mempool: readonly PendingTransaction[];
55
+ /** Current simulator time (independent of block numbers) */
56
+ currentTime: Date;
57
+ }>;
58
+ /** Result of a successful block production. */
59
+ export type BlockInfo = Readonly<{
60
+ blockNumber: bigint;
61
+ blockHash: string;
62
+ }>;
63
+ /**
64
+ * Request to produce a block with specific transactions, fullness, and optional strictness override. This mimics how
65
+ * real nodes work - selecting which transactions to include and how to validate them.
66
+ *
67
+ * The block producer can optionally specify a strictness that overrides per-transaction strictness. This enables
68
+ * patterns like:
69
+ *
70
+ * - Enforcing balancing for all blocks after genesis
71
+ * - Using a decorator pattern to modify strictness behavior
72
+ * - Testing specific validation scenarios
73
+ */
74
+ export type BlockProductionRequest = Readonly<{
75
+ /** Transactions to include in this block with strictness assigned */
76
+ transactions: readonly ReadyTransaction[];
77
+ /** Block fullness (0-1) for fee calculation */
78
+ fullness: number;
79
+ }>;
80
+ /**
81
+ * A block producer is a stream transformer that decides when blocks should be produced.
82
+ *
83
+ * It receives a stream of simulator state changes and transforms it into a stream of block production requests. Each
84
+ * request specifies which transactions to include and the block fullness for fee calculation.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // Custom producer: produce block when mempool has 5+ transactions
89
+ * const batchedProducer: BlockProducer = (states) =>
90
+ * states.pipe(
91
+ * Stream.filter((s) => s.mempool.length >= 5),
92
+ * Stream.map((s) => ({
93
+ * transactions: [...s.mempool],
94
+ * fullness: 0.5,
95
+ * }))
96
+ * );
97
+ * ```;
98
+ */
99
+ export type BlockProducer = (states: Stream.Stream<SimulatorState>) => Stream.Stream<BlockProductionRequest>;
100
+ /** Fullness specification: static number or callback based on state. */
101
+ export type FullnessSpec = number | ((state: SimulatorState) => number);
102
+ /**
103
+ * Genesis mint specification for shielded tokens.
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const mint: ShieldedGenesisMint = {
108
+ * type: 'shielded',
109
+ * tokenType: ledger.shieldedToken().raw,
110
+ * amount: 1000n,
111
+ * recipient: secretKeys,
112
+ * };
113
+ * ```;
114
+ */
115
+ export type ShieldedGenesisMint = Readonly<{
116
+ type: 'shielded';
117
+ tokenType: RawTokenType;
118
+ amount: bigint;
119
+ recipient: ZswapSecretKeys;
120
+ }>;
121
+ /**
122
+ * Genesis mint specification for unshielded tokens.
123
+ *
124
+ * For **custom tokens**: Minted directly from nothing (enforceBalancing disabled).
125
+ *
126
+ * For **Night tokens** (native token): Requires `verifyingKey` field. Night cannot be minted from nothing due to supply
127
+ * invariant, so the reward/claim mechanism is used internally. Night is auto-detected by comparing `tokenType` with
128
+ * `ledger.nativeToken().raw`.
129
+ *
130
+ * Note: Night claims have a minimum amount requirement (~14077). Use larger amounts to ensure the claim transaction
131
+ * succeeds.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * // Custom unshielded token
136
+ * const customMint: UnshieldedGenesisMint = {
137
+ * type: 'unshielded',
138
+ * tokenType: customToken,
139
+ * amount: 1000n,
140
+ * recipient: userAddress,
141
+ * };
142
+ *
143
+ * // Night token (native token) - requires verifyingKey
144
+ * const nightMint: UnshieldedGenesisMint = {
145
+ * type: 'unshielded',
146
+ * tokenType: ledger.nativeToken().raw,
147
+ * amount: 1_000_000n, // Must exceed minimum claim amount (~14077)
148
+ * recipient: userAddress,
149
+ * verifyingKey: signatureVerifyingKey,
150
+ * };
151
+ * ```;
152
+ */
153
+ export type UnshieldedGenesisMint = Readonly<{
154
+ type: 'unshielded';
155
+ tokenType: RawTokenType;
156
+ amount: bigint;
157
+ recipient: UserAddress;
158
+ /** Required for Night tokens (native token). Used for the claim transaction signature. */
159
+ verifyingKey?: SignatureVerifyingKey;
160
+ }>;
161
+ /**
162
+ * Genesis mint specification for initializing the simulator with pre-funded accounts.
163
+ *
164
+ * Uses a tagged union pattern consistent with the facade API:
165
+ *
166
+ * - **Shielded**: `{ type: 'shielded', tokenType, amount, recipient: ZswapSecretKeys }`
167
+ * - **Unshielded**: `{ type: 'unshielded', tokenType, amount, recipient, verifyingKey? }`
168
+ *
169
+ * - Custom tokens: minted directly (verifyingKey not needed)
170
+ * - Night tokens: auto-detected by tokenType, uses reward/claim mechanism (verifyingKey required)
171
+ */
172
+ export type GenesisMint = ShieldedGenesisMint | UnshieldedGenesisMint;
173
+ /** Configuration for well-formedness strictness checks. All options default to false for testing flexibility. */
174
+ export type StrictnessConfig = Readonly<{
175
+ enforceBalancing?: boolean;
176
+ verifyNativeProofs?: boolean;
177
+ verifyContractProofs?: boolean;
178
+ enforceLimits?: boolean;
179
+ verifySignatures?: boolean;
180
+ }>;
181
+ /**
182
+ * Default strictness for post-genesis blocks.
183
+ *
184
+ * In a realistic simulation:
185
+ *
186
+ * - Signatures should be verified (verifySignatures: true)
187
+ * - Proofs cannot be verified because they're erased (verifyNativeProofs/verifyContractProofs: false)
188
+ * - Limits should be enforced (enforceLimits: true)
189
+ * - Balancing must be enforced (enforceBalancing: true) - transactions must pay fees
190
+ *
191
+ * Note: Genesis blocks typically disable all strictness to allow initial token distribution.
192
+ */
193
+ export declare const defaultStrictness: StrictnessConfig;
194
+ /** Strictness for genesis blocks - all checks disabled to allow initial token distribution. */
195
+ export declare const genesisStrictness: StrictnessConfig;
196
+ /**
197
+ * Assign strictness to a pending transaction, creating a ready transaction. If the pending transaction already has
198
+ * strictness, use it; otherwise use the provided default.
199
+ */
200
+ export declare const assignStrictness: (pendingTx: PendingTransaction, defaultStrictness: WellFormedStrictness) => ReadyTransaction;
201
+ /**
202
+ * Assign strictness to all pending transactions, creating ready transactions. Transactions with existing strictness
203
+ * keep their strictness; others get the default.
204
+ */
205
+ export declare const assignStrictnessToAll: (transactions: readonly PendingTransaction[], defaultStrictness: WellFormedStrictness) => readonly ReadyTransaction[];
206
+ /** Get the last produced block, or undefined if no blocks yet. */
207
+ export declare const getLastBlock: (state: SimulatorState) => Block;
208
+ /** Get the current block number (height of the last block, or 0 if no blocks). */
209
+ export declare const getCurrentBlockNumber: (state: SimulatorState) => bigint;
210
+ /** Get a block by its number. */
211
+ export declare const getBlockByNumber: {
212
+ (number: bigint): (state: SimulatorState) => Block | undefined;
213
+ (state: SimulatorState, number: bigint): Block | undefined;
214
+ };
215
+ /** Get all transaction results from the last block. */
216
+ export declare const getLastBlockResults: (state: SimulatorState) => readonly TransactionResult[];
217
+ /** Get all events from the last block (flattened from all transactions). */
218
+ export declare const getLastBlockEvents: (state: SimulatorState) => readonly TransactionResult["events"][number][];
219
+ /**
220
+ * Get all events from blocks with number >= fromBlockNumber. Returns events ordered by block number, with each block's
221
+ * transactions flattened.
222
+ *
223
+ * Use this with the wallet's next-to-process index: `appliedIndex` after processing should be set to `lastBlockNumber +
224
+ * 1`, not `lastBlockNumber`.
225
+ */
226
+ export declare const getBlockEventsFrom: {
227
+ (fromBlockNumber: bigint): (state: SimulatorState) => readonly TransactionResult['events'][number][];
228
+ (state: SimulatorState, fromBlockNumber: bigint): readonly TransactionResult['events'][number][];
229
+ };
230
+ /**
231
+ * @deprecated Use getBlockEventsFrom instead with proper appliedIndex semantics Get all events from blocks with number
232
+ *
233
+ * > AfterBlockNumber.
234
+ */
235
+ export declare const getBlockEventsSince: {
236
+ (afterBlockNumber: bigint): (state: SimulatorState) => readonly TransactionResult['events'][number][];
237
+ (state: SimulatorState, afterBlockNumber: bigint): readonly TransactionResult['events'][number][];
238
+ };
239
+ /** Check if there are pending transactions in the mempool. */
240
+ export declare const hasPendingTransactions: (state: SimulatorState) => boolean;
241
+ /** Get the current simulator time. */
242
+ export declare const getCurrentTime: (state: SimulatorState) => Date;
243
+ /** Resolve fullness from spec and state. */
244
+ export declare const resolveFullness: (spec: FullnessSpec, state: SimulatorState) => number;
245
+ /** Create a block production request that includes all mempool transactions. */
246
+ export declare const allMempoolTransactions: (state: SimulatorState, fullness: number, defaultStrictness: WellFormedStrictness) => BlockProductionRequest;
247
+ /** Create a blank initial state. */
248
+ export declare const blankState: (networkId: NetworkId.NetworkId) => Promise<SimulatorState>;
249
+ /** Add a pending transaction to the mempool. */
250
+ export declare const addToMempool: (state: SimulatorState, pendingTx: PendingTransaction) => SimulatorState;
251
+ /** Remove transactions from the mempool. */
252
+ export declare const removeFromMempool: (state: SimulatorState, transactions: readonly ReadyTransaction[]) => SimulatorState;
253
+ /** Advance the simulator time by the given number of seconds. */
254
+ export declare const advanceTime: (state: SimulatorState, seconds: bigint) => SimulatorState;
255
+ /** Update the ledger state. */
256
+ export declare const updateLedger: (state: SimulatorState, ledger: LedgerState) => SimulatorState;
257
+ /** Append a block to the state and update time. */
258
+ export declare const appendBlock: (state: SimulatorState, block: Block, newLedger: LedgerState) => SimulatorState;
259
+ /**
260
+ * Pure state transition: apply a transaction to the simulator state. Returns Either with the new state or an error.
261
+ *
262
+ * @param state - Current simulator state
263
+ * @param tx - Transaction to apply
264
+ * @param strictness - Well-formedness strictness options
265
+ * @param blockContext - Block context for the transaction
266
+ * @param options - Optional parameters
267
+ * @param options.blockNumber - Override block number (defaults to last block + 1)
268
+ * @param options.blockFullness - Override detailed block fullness (SyntheticCost)
269
+ * @param options.overallBlockFullness - Override overall block fullness (0-1 value)
270
+ */
271
+ export declare const applyTransaction: (state: SimulatorState, tx: ProofErasedTransaction, strictness: WellFormedStrictness, blockContext: BlockContext, options?: {
272
+ blockNumber?: bigint;
273
+ blockFullness?: SyntheticCost;
274
+ overallBlockFullness?: number;
275
+ }) => Either.Either<[Block, SimulatorState], LedgerOps.LedgerError>;
276
+ /** Result of processing a single transaction. */
277
+ export type TransactionProcessingResult = Readonly<{
278
+ tx: ProofErasedTransaction;
279
+ result: TransactionResult;
280
+ newLedger: LedgerState;
281
+ }>;
282
+ /**
283
+ * Process a single pending transaction against the current ledger. Returns Either with the processing result or an
284
+ * error.
285
+ *
286
+ * @param ledger - Current ledger state
287
+ * @param readyTx - Transaction to process
288
+ * @param blockTime - Block timestamp
289
+ * @param blockContext - Block context
290
+ * @param minFullness - Minimum block fullness to use
291
+ */
292
+ export declare const processTransaction: (ledger: LedgerState, readyTx: ReadyTransaction, blockTime: Date, blockContext: BlockContext, minFullness: number) => Either.Either<TransactionProcessingResult, LedgerOps.LedgerError>;
293
+ /**
294
+ * Process multiple transactions in sequence, accumulating results. Returns Either with all results and final ledger, or
295
+ * first error. Each transaction uses its assigned strictness (from ReadyTransaction).
296
+ *
297
+ * @param ledger - Initial ledger state
298
+ * @param transactions - Transactions to process (each with assigned strictness)
299
+ * @param blockTime - Block timestamp
300
+ * @param blockContext - Block context
301
+ * @param fullness - Block fullness (0-1)
302
+ */
303
+ export declare const processTransactions: (ledger: LedgerState, transactions: readonly ReadyTransaction[], blockTime: Date, blockContext: BlockContext, fullness: number) => Either.Either<{
304
+ blockTransactions: readonly BlockTransaction[];
305
+ finalLedger: LedgerState;
306
+ }, LedgerOps.LedgerError>;
307
+ /**
308
+ * Create a block from processed transactions and update state. Pure function that takes pre-computed block hash.
309
+ *
310
+ * @param state - Current simulator state
311
+ * @param blockTransactions - Processed transactions to include
312
+ * @param blockHash - Pre-computed block hash
313
+ * @param blockTime - Block timestamp
314
+ * @param newLedger - New ledger state after processing transactions
315
+ * @param processedTxs - Ready transactions to remove from mempool
316
+ */
317
+ export declare const createBlock: (state: SimulatorState, blockTransactions: readonly BlockTransaction[], blockHashValue: string, blockTime: Date, newLedger: LedgerState, processedTxs: readonly ReadyTransaction[]) => [Block, SimulatorState];
318
+ /**
319
+ * Create an empty block (no transactions) and update state.
320
+ *
321
+ * @param state - Current simulator state
322
+ * @param blockHashValue - Pre-computed block hash
323
+ * @param blockTime - Block timestamp
324
+ * @param processedTxs - Original pending transactions to remove from mempool (if any)
325
+ */
326
+ export declare const createEmptyBlock: (state: SimulatorState, blockHashValue: string, blockTime: Date, processedTxs?: readonly ReadyTransaction[]) => [Block, SimulatorState];
327
+ /**
328
+ * Create a WellFormedStrictness instance with configurable options. All options default to false for maximum testing
329
+ * flexibility.
330
+ *
331
+ * Note: WellFormedStrictness is a class from the ledger library that requires mutation to configure. This is
332
+ * unavoidable given the external API design.
333
+ */
334
+ export declare const createStrictness: (config?: StrictnessConfig) => WellFormedStrictness;
335
+ /**
336
+ * Compute block hash from block number. Uses a deterministic hash based on block number for easy recomputation.
337
+ *
338
+ * @param blockNumber - The block number to compute hash for
339
+ * @returns A deterministic 64-character hex hash
340
+ */
341
+ export declare const blockHash: (blockNumber: bigint) => Promise<string>;
342
+ /**
343
+ * Create the next block context from the previous block.
344
+ *
345
+ * @param previousBlock - The previous block (or undefined for genesis)
346
+ * @param blockTime - The timestamp for the new block
347
+ * @returns A BlockContext suitable for transaction processing
348
+ */
349
+ export declare const nextBlockContextFromBlock: (previousBlock: Block | undefined, blockTime: Date) => Promise<BlockContext>;
@@ -0,0 +1,323 @@
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
+ /**
23
+ * Default strictness for post-genesis blocks.
24
+ *
25
+ * In a realistic simulation:
26
+ *
27
+ * - Signatures should be verified (verifySignatures: true)
28
+ * - Proofs cannot be verified because they're erased (verifyNativeProofs/verifyContractProofs: false)
29
+ * - Limits should be enforced (enforceLimits: true)
30
+ * - Balancing must be enforced (enforceBalancing: true) - transactions must pay fees
31
+ *
32
+ * Note: Genesis blocks typically disable all strictness to allow initial token distribution.
33
+ */
34
+ export const defaultStrictness = {
35
+ enforceBalancing: true,
36
+ verifyNativeProofs: false,
37
+ verifyContractProofs: false,
38
+ enforceLimits: true,
39
+ verifySignatures: true,
40
+ };
41
+ /** Strictness for genesis blocks - all checks disabled to allow initial token distribution. */
42
+ export const genesisStrictness = {
43
+ enforceBalancing: false,
44
+ verifyNativeProofs: false,
45
+ verifyContractProofs: false,
46
+ enforceLimits: false,
47
+ verifySignatures: false,
48
+ };
49
+ /**
50
+ * Assign strictness to a pending transaction, creating a ready transaction. If the pending transaction already has
51
+ * strictness, use it; otherwise use the provided default.
52
+ */
53
+ export const assignStrictness = (pendingTx, defaultStrictness) => ({
54
+ tx: pendingTx.tx,
55
+ strictness: pendingTx.strictness ?? defaultStrictness,
56
+ });
57
+ /**
58
+ * Assign strictness to all pending transactions, creating ready transactions. Transactions with existing strictness
59
+ * keep their strictness; others get the default.
60
+ */
61
+ export const assignStrictnessToAll = (transactions, defaultStrictness) => transactions.map((tx) => assignStrictness(tx, defaultStrictness));
62
+ // =============================================================================
63
+ // State Accessors (Pure Functions)
64
+ // =============================================================================
65
+ /** Get the last produced block, or undefined if no blocks yet. */
66
+ export const getLastBlock = (state) => EArray.lastNonEmpty(state.blocks);
67
+ /** Get the current block number (height of the last block, or 0 if no blocks). */
68
+ export const getCurrentBlockNumber = (state) => getLastBlock(state)?.number;
69
+ /** Get a block by its number. */
70
+ export const getBlockByNumber = EFunction.dual(2, (state, number) => state.blocks.find((b) => b.number === number));
71
+ /** Get all transaction results from the last block. */
72
+ export const getLastBlockResults = (state) => getLastBlock(state)?.transactions.map((t) => t.result) ?? [];
73
+ /** Get all events from the last block (flattened from all transactions). */
74
+ export const getLastBlockEvents = (state) => getLastBlockResults(state).flatMap((r) => r.events);
75
+ /**
76
+ * Get all events from blocks with number >= fromBlockNumber. Returns events ordered by block number, with each block's
77
+ * transactions flattened.
78
+ *
79
+ * Use this with the wallet's next-to-process index: `appliedIndex` after processing should be set to `lastBlockNumber +
80
+ * 1`, not `lastBlockNumber`.
81
+ */
82
+ export const getBlockEventsFrom = EFunction.dual(2, (state, fromBlockNumber) => state.blocks
83
+ .filter((b) => b.number >= fromBlockNumber)
84
+ .flatMap((b) => b.transactions.flatMap((t) => t.result.events)));
85
+ /**
86
+ * @deprecated Use getBlockEventsFrom instead with proper appliedIndex semantics Get all events from blocks with number
87
+ *
88
+ * > AfterBlockNumber.
89
+ */
90
+ export const getBlockEventsSince = EFunction.dual(2, (state, afterBlockNumber) => state.blocks
91
+ .filter((b) => b.number > afterBlockNumber)
92
+ .flatMap((b) => b.transactions.flatMap((t) => t.result.events)));
93
+ /** Check if there are pending transactions in the mempool. */
94
+ export const hasPendingTransactions = (state) => state.mempool.length > 0;
95
+ /** Get the current simulator time. */
96
+ export const getCurrentTime = (state) => state.currentTime;
97
+ // =============================================================================
98
+ // State Transformations (Pure Functions)
99
+ // =============================================================================
100
+ /** Resolve fullness from spec and state. */
101
+ export const resolveFullness = (spec, state) => typeof spec === 'function' ? spec(state) : spec;
102
+ /** Create a block production request that includes all mempool transactions. */
103
+ export const allMempoolTransactions = (state, fullness, defaultStrictness) => ({
104
+ transactions: assignStrictnessToAll(state.mempool, defaultStrictness),
105
+ fullness,
106
+ });
107
+ /** Create a blank initial state. */
108
+ export const blankState = async (networkId) => {
109
+ const blankGenesis = {
110
+ number: 0n,
111
+ hash: await blockHash(0n),
112
+ timestamp: new Date(0),
113
+ transactions: [],
114
+ };
115
+ return {
116
+ networkId,
117
+ ledger: LedgerState.blank(networkId),
118
+ blocks: [blankGenesis],
119
+ mempool: [],
120
+ currentTime: new Date(0),
121
+ };
122
+ };
123
+ /** Add a pending transaction to the mempool. */
124
+ export const addToMempool = (state, pendingTx) => ({
125
+ ...state,
126
+ mempool: [...state.mempool, pendingTx],
127
+ });
128
+ /** Remove transactions from the mempool. */
129
+ export const removeFromMempool = (state, transactions) => {
130
+ const txsToRemove = new Set(transactions.map((t) => t.tx));
131
+ return {
132
+ ...state,
133
+ mempool: state.mempool.filter((pending) => !txsToRemove.has(pending.tx)),
134
+ };
135
+ };
136
+ /** Advance the simulator time by the given number of seconds. */
137
+ export const advanceTime = (state, seconds) => ({
138
+ ...state,
139
+ currentTime: DateOps.addSeconds(state.currentTime, seconds),
140
+ });
141
+ /** Update the ledger state. */
142
+ export const updateLedger = (state, ledger) => ({
143
+ ...state,
144
+ ledger,
145
+ });
146
+ /** Append a block to the state and update time. */
147
+ export const appendBlock = (state, block, newLedger) => ({
148
+ ...state,
149
+ ledger: newLedger,
150
+ blocks: [...state.blocks, block],
151
+ currentTime: block.timestamp,
152
+ });
153
+ // =============================================================================
154
+ // Transaction Application (Pure Function)
155
+ // =============================================================================
156
+ /**
157
+ * Pure state transition: apply a transaction to the simulator state. Returns Either with the new state or an error.
158
+ *
159
+ * @param state - Current simulator state
160
+ * @param tx - Transaction to apply
161
+ * @param strictness - Well-formedness strictness options
162
+ * @param blockContext - Block context for the transaction
163
+ * @param options - Optional parameters
164
+ * @param options.blockNumber - Override block number (defaults to last block + 1)
165
+ * @param options.blockFullness - Override detailed block fullness (SyntheticCost)
166
+ * @param options.overallBlockFullness - Override overall block fullness (0-1 value)
167
+ */
168
+ export const applyTransaction = (state, tx, strictness, blockContext, options) => {
169
+ return LedgerOps.ledgerTry(() => {
170
+ const computedFullness = options?.blockFullness ?? tx.cost(state.ledger.parameters);
171
+ const detailedBlockFullness = state.ledger.parameters.normalizeFullness(computedFullness);
172
+ const computedBlockFullness = options?.overallBlockFullness ??
173
+ Math.max(detailedBlockFullness.readTime, detailedBlockFullness.computeTime, detailedBlockFullness.blockUsage, detailedBlockFullness.bytesWritten, detailedBlockFullness.bytesChurned);
174
+ const blockNumber = options?.blockNumber ?? getCurrentBlockNumber(state) + 1n;
175
+ const blockTime = state.currentTime;
176
+ const verifiedTransaction = tx.wellFormed(state.ledger, strictness, blockTime);
177
+ const transactionContext = new TransactionContext(state.ledger, blockContext);
178
+ const [newLedgerState, txResult] = state.ledger.apply(verifiedTransaction, transactionContext);
179
+ const newBlock = {
180
+ number: blockNumber,
181
+ hash: blockContext.parentBlockHash,
182
+ timestamp: blockTime,
183
+ transactions: [{ tx, result: txResult }],
184
+ };
185
+ const newState = {
186
+ ...state,
187
+ ledger: newLedgerState.postBlockUpdate(blockTime, detailedBlockFullness, computedBlockFullness),
188
+ blocks: [...state.blocks, newBlock],
189
+ };
190
+ return [newBlock, newState];
191
+ });
192
+ };
193
+ /**
194
+ * Process a single pending transaction against the current ledger. Returns Either with the processing result or an
195
+ * error.
196
+ *
197
+ * @param ledger - Current ledger state
198
+ * @param readyTx - Transaction to process
199
+ * @param blockTime - Block timestamp
200
+ * @param blockContext - Block context
201
+ * @param minFullness - Minimum block fullness to use
202
+ */
203
+ export const processTransaction = (ledger, readyTx, blockTime, blockContext, minFullness) => {
204
+ return LedgerOps.ledgerTry(() => {
205
+ const computedFullness = readyTx.tx.cost(ledger.parameters);
206
+ const detailedBlockFullness = ledger.parameters.normalizeFullness(computedFullness);
207
+ const computedBlockFullness = Math.max(minFullness, detailedBlockFullness.readTime, detailedBlockFullness.computeTime, detailedBlockFullness.blockUsage, detailedBlockFullness.bytesWritten, detailedBlockFullness.bytesChurned);
208
+ // Use the assigned strictness
209
+ const verifiedTransaction = readyTx.tx.wellFormed(ledger, readyTx.strictness, blockTime);
210
+ const transactionContext = new TransactionContext(ledger, blockContext);
211
+ const [newLedgerState, txResult] = ledger.apply(verifiedTransaction, transactionContext);
212
+ const postBlockLedger = newLedgerState.postBlockUpdate(blockTime, detailedBlockFullness, computedBlockFullness);
213
+ return {
214
+ tx: readyTx.tx,
215
+ result: txResult,
216
+ newLedger: postBlockLedger,
217
+ };
218
+ });
219
+ };
220
+ /**
221
+ * Process multiple transactions in sequence, accumulating results. Returns Either with all results and final ledger, or
222
+ * first error. Each transaction uses its assigned strictness (from ReadyTransaction).
223
+ *
224
+ * @param ledger - Initial ledger state
225
+ * @param transactions - Transactions to process (each with assigned strictness)
226
+ * @param blockTime - Block timestamp
227
+ * @param blockContext - Block context
228
+ * @param fullness - Block fullness (0-1)
229
+ */
230
+ 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) => ({
231
+ blockTransactions: [...blockTransactions, { tx: result.tx, result: result.result }],
232
+ finalLedger: result.newLedger,
233
+ }))), Either.right({ blockTransactions: [], finalLedger: ledger }));
234
+ /**
235
+ * Create a block from processed transactions and update state. Pure function that takes pre-computed block hash.
236
+ *
237
+ * @param state - Current simulator state
238
+ * @param blockTransactions - Processed transactions to include
239
+ * @param blockHash - Pre-computed block hash
240
+ * @param blockTime - Block timestamp
241
+ * @param newLedger - New ledger state after processing transactions
242
+ * @param processedTxs - Ready transactions to remove from mempool
243
+ */
244
+ export const createBlock = (state, blockTransactions, blockHashValue, blockTime, newLedger, processedTxs) => {
245
+ const nextBlockNumber = getCurrentBlockNumber(state) + 1n;
246
+ const block = {
247
+ number: nextBlockNumber,
248
+ hash: blockHashValue,
249
+ timestamp: blockTime,
250
+ transactions: blockTransactions,
251
+ };
252
+ const txsToRemove = new Set(processedTxs.map((t) => t.tx));
253
+ const newState = {
254
+ ...state,
255
+ ledger: newLedger,
256
+ blocks: [...state.blocks, block],
257
+ mempool: state.mempool.filter((pending) => !txsToRemove.has(pending.tx)),
258
+ currentTime: blockTime,
259
+ };
260
+ return [block, newState];
261
+ };
262
+ /**
263
+ * Create an empty block (no transactions) and update state.
264
+ *
265
+ * @param state - Current simulator state
266
+ * @param blockHashValue - Pre-computed block hash
267
+ * @param blockTime - Block timestamp
268
+ * @param processedTxs - Original pending transactions to remove from mempool (if any)
269
+ */
270
+ export const createEmptyBlock = (state, blockHashValue, blockTime, processedTxs = []) => {
271
+ return createBlock(state, [], blockHashValue, blockTime, state.ledger, processedTxs);
272
+ };
273
+ // =============================================================================
274
+ // Helper Functions
275
+ // =============================================================================
276
+ /**
277
+ * Create a WellFormedStrictness instance with configurable options. All options default to false for maximum testing
278
+ * flexibility.
279
+ *
280
+ * Note: WellFormedStrictness is a class from the ledger library that requires mutation to configure. This is
281
+ * unavoidable given the external API design.
282
+ */
283
+ export const createStrictness = (config = {}) => {
284
+ const strictness = new WellFormedStrictness();
285
+ strictness.enforceBalancing = config.enforceBalancing ?? false;
286
+ strictness.verifyNativeProofs = config.verifyNativeProofs ?? false;
287
+ strictness.verifyContractProofs = config.verifyContractProofs ?? false;
288
+ strictness.enforceLimits = config.enforceLimits ?? false;
289
+ strictness.verifySignatures = config.verifySignatures ?? false;
290
+ return strictness;
291
+ };
292
+ /**
293
+ * Compute block hash from block number. Uses a deterministic hash based on block number for easy recomputation.
294
+ *
295
+ * @param blockNumber - The block number to compute hash for
296
+ * @returns A deterministic 64-character hex hash
297
+ */
298
+ export const blockHash = async (blockNumber) => {
299
+ const input = `block-${blockNumber.toString()}`;
300
+ const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
301
+ const { Encoding } = await import('effect');
302
+ return Encoding.encodeHex(new Uint8Array(hashBuffer));
303
+ };
304
+ /**
305
+ * Create the next block context from the previous block.
306
+ *
307
+ * @param previousBlock - The previous block (or undefined for genesis)
308
+ * @param blockTime - The timestamp for the new block
309
+ * @returns A BlockContext suitable for transaction processing
310
+ */
311
+ export const nextBlockContextFromBlock = async (previousBlock, blockTime) => {
312
+ const nextBlockNumber = previousBlock !== undefined ? previousBlock.number + 1n : 0n;
313
+ const hash = await blockHash(nextBlockNumber);
314
+ const blockSeconds = DateOps.dateToSeconds(blockTime);
315
+ const previousSeconds = previousBlock !== undefined ? DateOps.dateToSeconds(previousBlock.timestamp) : blockSeconds - 1n;
316
+ const timeSinceLastBlock = blockSeconds - previousSeconds;
317
+ return {
318
+ parentBlockHash: hash,
319
+ secondsSinceEpoch: blockSeconds,
320
+ secondsSinceEpochErr: 1, // Clock error tolerance in seconds (reasonable default for simulator)
321
+ lastBlockTime: timeSinceLastBlock > 0n ? timeSinceLastBlock : 1n,
322
+ };
323
+ };
@@ -0,0 +1,2 @@
1
+ export { getLastBlock, getCurrentBlockNumber, getCurrentTime, getBlockByNumber, getLastBlockResults, getLastBlockEvents, getBlockEventsFrom, getBlockEventsSince, hasPendingTransactions, resolveFullness, allMempoolTransactions, blankState, addToMempool, removeFromMempool, advanceTime, updateLedger, appendBlock, applyTransaction, 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 StrictnessConfig, } from './SimulatorState.js';
2
+ export { Simulator, immediateBlockProducer, type SimulatorConfig } from './Simulator.js';