@midnightntwrk/wallet-sdk-capabilities 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ /*
2
+ * This file is part of MIDNIGHT-WALLET-SDK.
3
+ * Copyright (C) Midnight Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * You may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { DateTime, DefaultServices, Duration, Effect, Exit, Iterable, Option, pipe, Schedule, Scope, Stream, SubscriptionRef, } from 'effect';
16
+ import * as PendingTransactions from './pendingTransactions.js';
17
+ import { HttpQueryClient } from '@midnightntwrk/wallet-sdk-indexer-client/effect';
18
+ import { TransactionStatus } from '@midnightntwrk/wallet-sdk-indexer-client';
19
+ import { EitherOps, ObservableOps } from '@midnightntwrk/wallet-sdk-utilities';
20
+ export class PendingTransactionsServiceImpl {
21
+ static init(initParams) {
22
+ return PendingTransactionsServiceImpl.initEffect(initParams).pipe(Effect.runPromise);
23
+ }
24
+ static restore(data, txTrait, configuration) {
25
+ return pipe(PendingTransactions.deserialize(data, txTrait), EitherOps.toEffect, Effect.andThen((state) => PendingTransactionsServiceImpl.initEffect({ txTrait, initialState: state, configuration })), Effect.runPromise);
26
+ }
27
+ static initEffect(initParams) {
28
+ return Effect.gen(function* () {
29
+ const service = new PendingTransactionsServiceEffectImpl(initParams.txTrait, initParams.initialState);
30
+ const scope = yield* Scope.make();
31
+ return new PendingTransactionsServiceImpl(service, scope, initParams.configuration);
32
+ });
33
+ }
34
+ #effectService;
35
+ #scope;
36
+ #configuration;
37
+ constructor(effectService, scope, configuration) {
38
+ this.#effectService = effectService;
39
+ this.#scope = scope;
40
+ this.#configuration = configuration;
41
+ }
42
+ addPendingTransaction(tx) {
43
+ return this.#effectService.addPendingTransaction(tx).pipe(Effect.runPromise);
44
+ }
45
+ clear(tx) {
46
+ return this.#effectService.clear(tx).pipe(Effect.runPromise);
47
+ }
48
+ start() {
49
+ return this.#effectService.startPolling(Stream.tick(Duration.seconds(1))).pipe(Effect.provide(HttpQueryClient.layer({
50
+ url: this.#configuration.indexerClientConnection.indexerHttpUrl,
51
+ })), Effect.provideService(Scope.Scope, this.#scope), Effect.provide(DefaultServices.liveServices), Effect.runFork, () => Promise.resolve());
52
+ }
53
+ state() {
54
+ return this.#effectService.state().pipe(ObservableOps.fromStream);
55
+ }
56
+ stop() {
57
+ return pipe(Scope.close(this.#scope, Exit.succeed(undefined)), Effect.runPromise);
58
+ }
59
+ }
60
+ export class PendingTransactionsServiceEffectImpl {
61
+ #state;
62
+ #txTrait;
63
+ static restore(data, txTrait) {
64
+ return pipe(data, (data) => PendingTransactions.deserialize(data, txTrait), EitherOps.toEffect, Effect.map((state) => new PendingTransactionsServiceEffectImpl(txTrait, state)));
65
+ }
66
+ constructor(txTrait, initialState) {
67
+ this.#txTrait = txTrait;
68
+ this.#state = SubscriptionRef.make(initialState ?? PendingTransactions.empty()).pipe(Effect.runSync); // Should not be here, but otherwise initialization would be too involved
69
+ }
70
+ state() {
71
+ return Stream.concat(Stream.fromEffect(SubscriptionRef.get(this.#state)), this.#state.changes);
72
+ }
73
+ startPolling(ticks) {
74
+ return ticks.pipe(Stream.mapEffect(() => SubscriptionRef.get(this.#state)), Stream.mapConcat(PendingTransactions.allPending), Stream.mapConcatEffect((item) => {
75
+ return Effect.gen(this, function* () {
76
+ const now = yield* DateTime.now;
77
+ const result = yield* this.queryForStatus(item.tx);
78
+ return Option.match(result, {
79
+ onSome: (status) => [{ ...item, result: status }],
80
+ onNone: () => {
81
+ const failedResult = {
82
+ status: 'FAILURE',
83
+ segments: [],
84
+ };
85
+ return this.#txTrait.hasTTLExpired(item.tx, item.creationTime, now)
86
+ ? [{ ...item, result: failedResult }]
87
+ : [];
88
+ },
89
+ });
90
+ });
91
+ }), Stream.retry(pipe(Schedule.exponential(Duration.millis(1)), Schedule.jitteredWith({ min: 0.1, max: 1.2 }), Schedule.resetAfter(Duration.minutes(5)))), Stream.catchAll((error) => {
92
+ return Stream.execute(Effect.logWarning(error, 'Caught error in PendingTransactionsService'));
93
+ }), Stream.runForEachScoped((item) => {
94
+ return this.saveResult(item.tx, item.result);
95
+ }));
96
+ }
97
+ addPendingTransaction(tx) {
98
+ return SubscriptionRef.updateEffect(this.#state, (state) => {
99
+ return DateTime.now.pipe(Effect.andThen((now) => PendingTransactions.addPendingTransaction(state, tx, now, this.#txTrait)));
100
+ });
101
+ }
102
+ clear(tx) {
103
+ return SubscriptionRef.update(this.#state, (state) => {
104
+ return PendingTransactions.clear(state, tx, this.#txTrait);
105
+ });
106
+ }
107
+ saveResult(tx, result) {
108
+ switch (result.status) {
109
+ case 'SUCCESS':
110
+ return this.clear(tx);
111
+ case 'FAILURE':
112
+ case 'PARTIAL_SUCCESS':
113
+ return SubscriptionRef.update(this.#state, (state) => {
114
+ return PendingTransactions.saveResult(state, tx, result, this.#txTrait);
115
+ });
116
+ }
117
+ }
118
+ queryForStatus(tx) {
119
+ return Effect.gen(this, function* () {
120
+ const statusQuery = yield* TransactionStatus;
121
+ const result = yield* statusQuery({ transactionId: this.#txTrait.firstId(tx) }).pipe(Effect.catchAll((error) => {
122
+ const fallback = { transactions: [] };
123
+ return pipe(Effect.logWarning(error, 'Observed error in PendingTransactionsService, retrying'), Effect.as(fallback));
124
+ }));
125
+ return pipe(result.transactions, Iterable.filterMap((res) => {
126
+ if (res.__typename == 'SystemTransaction') {
127
+ return Option.none();
128
+ }
129
+ if (res.transactionResult.status != 'SUCCESS' &&
130
+ res.transactionResult.status != 'FAILURE' &&
131
+ res.transactionResult.status != 'PARTIAL_SUCCESS') {
132
+ return Option.none();
133
+ }
134
+ if (this.#txTrait.areAllTxIdsIncluded(tx, res.identifiers)) {
135
+ return Option.some({
136
+ status: res.transactionResult.status,
137
+ segments: res.transactionResult.segments ?? [],
138
+ });
139
+ }
140
+ else {
141
+ return Option.none();
142
+ }
143
+ }), Iterable.head);
144
+ });
145
+ }
146
+ }
@@ -0,0 +1 @@
1
+ export * from './provingService.js';
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This file is part of MIDNIGHT-WALLET-SDK.
3
+ * Copyright (C) Midnight Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * You may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ export * from './provingService.js';
@@ -0,0 +1,37 @@
1
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
2
+ import type { KeyMaterialProvider } from '@midnight-ntwrk/zkir-v2';
3
+ import { type InvalidProtocolSchemeError } from '@midnightntwrk/wallet-sdk-utilities/networking';
4
+ import { Effect } from 'effect';
5
+ declare const ProvingError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "Wallet.Proving";
7
+ } & Readonly<A>;
8
+ export declare class ProvingError extends ProvingError_base<{
9
+ message: string;
10
+ cause: Error;
11
+ }> {
12
+ }
13
+ export interface ProvingServiceEffect<TTransaction> {
14
+ prove(transaction: ledger.UnprovenTransaction): Effect.Effect<TTransaction, ProvingError>;
15
+ }
16
+ export interface ProvingService<TTransaction> {
17
+ prove(transaction: ledger.UnprovenTransaction): Promise<TTransaction>;
18
+ }
19
+ export type UnboundTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proof, ledger.PreBinding>;
20
+ export declare const fromProvingProviderEffect: (provider: Effect.Effect<ledger.ProvingProvider, InvalidProtocolSchemeError>) => ProvingServiceEffect<UnboundTransaction>;
21
+ export declare const fromProvingProvider: (provider: ledger.ProvingProvider) => ProvingServiceEffect<UnboundTransaction>;
22
+ export type ServerProvingConfiguration = {
23
+ provingServerUrl: URL;
24
+ };
25
+ export type WasmProvingConfiguration = {
26
+ keyMaterialProvider?: KeyMaterialProvider;
27
+ };
28
+ export type DefaultProvingConfiguration = ServerProvingConfiguration;
29
+ export declare const makeServerProvingServiceEffect: (configuration: ServerProvingConfiguration) => ProvingServiceEffect<UnboundTransaction>;
30
+ export declare const makeWasmProvingServiceEffect: (configuration?: WasmProvingConfiguration) => ProvingServiceEffect<UnboundTransaction>;
31
+ export declare const makeSimulatorProvingServiceEffect: () => ProvingServiceEffect<ledger.ProofErasedTransaction>;
32
+ export declare const makeDefaultProvingServiceEffect: (configuration: DefaultProvingConfiguration) => ProvingServiceEffect<UnboundTransaction>;
33
+ export declare const makeDefaultProvingService: (configuration: DefaultProvingConfiguration) => ProvingService<UnboundTransaction>;
34
+ export declare const makeServerProvingService: (configuration: ServerProvingConfiguration) => ProvingService<UnboundTransaction>;
35
+ export declare const makeWasmProvingService: (configuration?: WasmProvingConfiguration) => ProvingService<UnboundTransaction>;
36
+ export declare const makeSimulatorProvingService: () => ProvingService<ledger.ProofErasedTransaction>;
37
+ export {};
@@ -0,0 +1,61 @@
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
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
14
+ import { HttpProverClient, WasmProver } from '@midnightntwrk/wallet-sdk-prover-client/effect';
15
+ import { ClientError, ServerError, } from '@midnightntwrk/wallet-sdk-utilities/networking';
16
+ import { Data, Effect, pipe } from 'effect';
17
+ export class ProvingError extends Data.TaggedError('Wallet.Proving') {
18
+ }
19
+ const wrapEffectService = (effectService) => ({
20
+ prove: (transaction) => Effect.runPromise(effectService.prove(transaction)),
21
+ });
22
+ export const fromProvingProviderEffect = (provider) => {
23
+ return {
24
+ prove(transaction) {
25
+ return pipe(provider, Effect.flatMap((provider) => Effect.tryPromise({
26
+ try: () => transaction.prove(provider, ledger.CostModel.initialCostModel()),
27
+ catch: (error) => error instanceof ClientError || error instanceof ServerError
28
+ ? error
29
+ : new ClientError({ message: 'Failed to prove transaction', cause: error }),
30
+ })), Effect.catchAll((error) => Effect.fail(new ProvingError({
31
+ message: error.message,
32
+ cause: error,
33
+ }))));
34
+ },
35
+ };
36
+ };
37
+ export const fromProvingProvider = (provider) => {
38
+ return fromProvingProviderEffect(Effect.succeed(provider));
39
+ };
40
+ export const makeServerProvingServiceEffect = (configuration) => {
41
+ return pipe(HttpProverClient.create({
42
+ url: configuration.provingServerUrl,
43
+ }), Effect.map((client) => client.asProvingProvider()), fromProvingProviderEffect);
44
+ };
45
+ export const makeWasmProvingServiceEffect = (configuration) => {
46
+ return pipe(WasmProver.create({
47
+ keyMaterialProvider: configuration?.keyMaterialProvider ?? WasmProver.makeDefaultKeyMaterialProvider(),
48
+ }), Effect.map((prover) => prover.asProvingProvider()), fromProvingProviderEffect);
49
+ };
50
+ export const makeSimulatorProvingServiceEffect = () => {
51
+ return {
52
+ prove(transaction) {
53
+ return Effect.succeed(transaction.eraseProofs());
54
+ },
55
+ };
56
+ };
57
+ export const makeDefaultProvingServiceEffect = (configuration) => makeServerProvingServiceEffect(configuration);
58
+ export const makeDefaultProvingService = (configuration) => wrapEffectService(makeDefaultProvingServiceEffect(configuration));
59
+ export const makeServerProvingService = (configuration) => wrapEffectService(makeServerProvingServiceEffect(configuration));
60
+ export const makeWasmProvingService = (configuration) => wrapEffectService(makeWasmProvingServiceEffect(configuration));
61
+ export const makeSimulatorProvingService = () => wrapEffectService(makeSimulatorProvingServiceEffect());
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Unified Simulator for wallet testing.
3
+ *
4
+ * This module provides a simulated ledger environment for testing wallet functionality without requiring a real
5
+ * blockchain node. It supports:
6
+ *
7
+ * - Optional genesis mints for pre-funded accounts (shielded, unshielded, or Night tokens)
8
+ * - Transaction submission with configurable strictness
9
+ * - Night token rewards via rewardNight()
10
+ * - Time advancement for TTL and time-sensitive tests
11
+ */
12
+ import { type Array as Arr, Effect, type Scope, Stream, SubscriptionRef } from 'effect';
13
+ import { type ProofErasedTransaction, type SignatureVerifyingKey } from '@midnight-ntwrk/ledger-v8';
14
+ import { LedgerOps } from '@midnightntwrk/wallet-sdk-utilities';
15
+ import { NetworkId } from '@midnightntwrk/wallet-sdk-abstractions';
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';
19
+ /**
20
+ * Default block producer: produces a block for each state change with non-empty mempool.
21
+ *
22
+ * By default, uses post-genesis strictness (balancing, signatures, limits enforced). This ensures realistic simulation
23
+ * where transactions must be properly balanced (pay fees).
24
+ *
25
+ * @param fullness - Static fullness (0-1) or callback based on state
26
+ * @param strictness - Strictness config (defaults to defaultStrictness)
27
+ */
28
+ export declare const immediateBlockProducer: (fullness?: FullnessSpec, strictness?: StrictnessConfig) => BlockProducer;
29
+ /** Simulator initialization configuration. */
30
+ export type SimulatorConfig = Readonly<{
31
+ /**
32
+ * Pre-funded accounts to create at genesis. When provided, creates a genesis block with transactions minting tokens
33
+ * to recipients. When omitted, the simulator starts with an empty ledger.
34
+ */
35
+ genesisMints?: Arr.NonEmptyArray<GenesisMint>;
36
+ /** Network identifier. Defaults to Undeployed. */
37
+ networkId?: NetworkId.NetworkId;
38
+ /** Custom block producer. Defaults to immediateBlockProducer(). */
39
+ blockProducer?: BlockProducer;
40
+ }>;
41
+ /**
42
+ * Unified simulator for wallet testing.
43
+ *
44
+ * Provides a simulated ledger environment for testing wallet functionality without a real blockchain. Optionally
45
+ * pre-funds accounts via genesis mints.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // Empty ledger (useful for dust/Night token testing via rewardNight)
50
+ * const simulator = yield* Simulator.init({});
51
+ *
52
+ * // Pre-funded accounts (useful for token transfer testing)
53
+ * const simulator = yield* Simulator.init({
54
+ * genesisMints: [{ amount: 1000n, tokenType, shieldedRecipient: secretKeys }],
55
+ * });
56
+ * ```;
57
+ */
58
+ export declare class Simulator {
59
+ #private;
60
+ /**
61
+ * Initialize a new simulator.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * // Empty ledger - use rewardNight() for Night tokens
66
+ * const simulator = yield* Simulator.init({});
67
+ *
68
+ * // Pre-funded accounts for token transfer testing
69
+ * const simulator = yield* Simulator.init({
70
+ * genesisMints: [{ amount: 1000n, tokenType, shieldedRecipient: secretKeys }],
71
+ * });
72
+ *
73
+ * // With custom network ID
74
+ * const simulator = yield* Simulator.init({
75
+ * networkId: NetworkId.Preview,
76
+ * genesisMints: [...],
77
+ * });
78
+ * ```;
79
+ *
80
+ * @param config - Configuration options (all optional)
81
+ * @returns Effect that produces a Simulator instance
82
+ */
83
+ static init(config?: SimulatorConfig): Effect.Effect<Simulator, never, Scope.Scope>;
84
+ /** Initialize simulator with blank ledger state. */
85
+ private static initBlank;
86
+ /**
87
+ * 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.
89
+ */
90
+ private static initWithGenesis;
91
+ /** Create a Simulator from an initial state with proper stream setup. */
92
+ private static fromState;
93
+ /** Observable stream of simulator state changes. */
94
+ readonly state$: Stream.Stream<SimulatorState>;
95
+ constructor(stateRef: SubscriptionRef.SubscriptionRef<SimulatorState>, state$: Stream.Stream<SimulatorState>);
96
+ /** Get the current simulator state. */
97
+ getLatestState(): Effect.Effect<SimulatorState>;
98
+ /**
99
+ * Distribute Night tokens to a recipient and submit claim transaction to mempool. Used for testing dust token
100
+ * generation.
101
+ *
102
+ * This method:
103
+ *
104
+ * 1. Modifies the ledger to make Night tokens claimable
105
+ * 2. Creates and submits a ClaimRewardsTransaction to the mempool
106
+ * 3. The block producer will process the transaction
107
+ *
108
+ * @param verifyingKey - Signature verifying key (recipient address is derived from it)
109
+ * @param amount - Amount of Night tokens to distribute
110
+ */
111
+ rewardNight(verifyingKey: SignatureVerifyingKey, amount: bigint): Effect.Effect<Block, LedgerOps.LedgerError>;
112
+ /**
113
+ * Submit a transaction and wait for it to be included in a block.
114
+ *
115
+ * This method adds the transaction to the mempool and blocks until the block producer includes it in a block. Use
116
+ * this when you need confirmation that the transaction was processed.
117
+ *
118
+ * For fire-and-forget scenarios where you don't need to wait for block inclusion, use `submitAndForget` instead.
119
+ *
120
+ * @param tx - Transaction to submit (proofs erased)
121
+ * @param options - Optional submission options
122
+ * @param options.strictness - Override well-formedness strictness
123
+ * @returns The block containing the transaction
124
+ */
125
+ submitTransaction(tx: ProofErasedTransaction, options?: {
126
+ strictness?: StrictnessConfig;
127
+ }): Effect.Effect<Block, LedgerOps.LedgerError>;
128
+ /**
129
+ * Submit a transaction without waiting for block inclusion.
130
+ *
131
+ * This method adds the transaction to the mempool and returns immediately. The block producer will process it
132
+ * asynchronously. Use this for fire-and-forget scenarios or when testing custom block producers with batched
133
+ * transactions.
134
+ *
135
+ * To wait for block inclusion, use `submitTransaction` instead.
136
+ *
137
+ * @param tx - Transaction to submit (proofs erased)
138
+ * @param options - Optional options
139
+ * @param options.strictness - Override well-formedness strictness
140
+ */
141
+ submitAndForget(tx: ProofErasedTransaction, options?: {
142
+ strictness?: StrictnessConfig;
143
+ }): Effect.Effect<void>;
144
+ /**
145
+ * Fast-forward the simulator time by the given number of seconds. Does not produce a block - only advances the
146
+ * internal clock. Useful for testing time-sensitive functionality like TTL.
147
+ *
148
+ * @param seconds - Number of seconds to advance (must be positive)
149
+ */
150
+ fastForward(seconds: bigint): Effect.Effect<void>;
151
+ /**
152
+ * Query the simulator state with a custom function. This is a generic query mechanism that allows extracting any
153
+ * information from the current state without modifying it.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * // Query fee prices
158
+ * const feePrices = yield* simulator.query(state => state.ledger.parameters.feePrices);
159
+ *
160
+ * // Use composable state accessors
161
+ * const blockNumber = yield* simulator.query(getCurrentBlockNumber);
162
+ * const lastBlock = yield* simulator.query(getLastBlock);
163
+ * const events = yield* simulator.query(getLastBlockEvents);
164
+ *
165
+ * // Query UTXOs for an address
166
+ * const utxos = yield* simulator.query(state => Array.from(state.ledger.utxo.filter(address)));
167
+ *
168
+ * // Complex query returning multiple values
169
+ * const info = yield* simulator.query(state => ({
170
+ * networkId: state.networkId,
171
+ * blockNumber: getCurrentBlockNumber(state),
172
+ * feePrices: state.ledger.parameters.feePrices,
173
+ * }));
174
+ * ```;
175
+ *
176
+ * @param fn - Function that receives the current state and returns a result
177
+ * @returns The result of applying the function to the current state
178
+ */
179
+ query<T>(fn: (state: SimulatorState) => T): Effect.Effect<T>;
180
+ }