@midnightntwrk/wallet-sdk-dust-wallet 4.2.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,105 @@
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 { DustLocalState, } from '@midnight-ntwrk/ledger-v8';
14
+ import { ProtocolVersion, SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
15
+ import { DateOps } from '@midnightntwrk/wallet-sdk-utilities';
16
+ import { Array as Arr, Option, pipe } from 'effect';
17
+ export const PublicKey = {
18
+ fromSecretKey: (secretKey) => {
19
+ return {
20
+ publicKey: secretKey.publicKey,
21
+ };
22
+ },
23
+ };
24
+ export const CoreWallet = {
25
+ init(localState, secretKey, networkId) {
26
+ return CoreWallet.empty(localState, PublicKey.fromSecretKey(secretKey), networkId);
27
+ },
28
+ initEmpty(dustParameters, secretKey, networkId) {
29
+ return CoreWallet.empty(new DustLocalState(dustParameters), PublicKey.fromSecretKey(secretKey), networkId);
30
+ },
31
+ empty(localState, publicKey, networkId) {
32
+ return {
33
+ state: localState,
34
+ publicKey,
35
+ networkId,
36
+ pendingDust: [],
37
+ progress: SyncProgress.createSyncProgress(),
38
+ protocolVersion: ProtocolVersion.MinSupportedVersion,
39
+ };
40
+ },
41
+ restore(localState, publicKey, pendingTokens, syncProgress, protocolVersion, networkId) {
42
+ return {
43
+ state: localState,
44
+ publicKey,
45
+ networkId,
46
+ pendingDust: pendingTokens,
47
+ progress: SyncProgress.createSyncProgress(syncProgress),
48
+ protocolVersion: ProtocolVersion.ProtocolVersion(protocolVersion),
49
+ };
50
+ },
51
+ applyEventsWithChanges(wallet, secretKey, events, currentTime) {
52
+ // TODO: replace currentTime with `updatedState.syncTime` introduced in ledger-6.2.0-rc.1
53
+ const stateWithChanges = wallet.state.replayEventsWithChanges(secretKey, events);
54
+ const updatedState = stateWithChanges.state.processTtls(currentTime);
55
+ const availableNonces = updatedState.utxos.map((utxo) => utxo.nonce);
56
+ return [
57
+ {
58
+ ...wallet,
59
+ state: updatedState,
60
+ pendingDust: wallet.pendingDust.filter((t) => availableNonces.includes(t.nonce)),
61
+ },
62
+ stateWithChanges.changes,
63
+ ];
64
+ },
65
+ applyFailed(wallet, tx) {
66
+ const pendingSpendsMap = CoreWallet.pendingDustToMap(wallet.pendingDust);
67
+ const relevantSpends = pipe([...(tx.intents?.values() ?? [])], Arr.flatMap((intent) => {
68
+ const spendTime = intent.dustActions?.ctime;
69
+ return (intent.dustActions?.spends ?? []).map((spend) => ({ spend, spendTime }));
70
+ }), Arr.filterMap(({ spend, spendTime }) => pipe(Option.fromNullable(pendingSpendsMap.get(spend.oldNullifier)), Option.map(() => ({ spend, spendTime })))));
71
+ const [updatedState, removedNullifiers] = pipe(relevantSpends, Arr.reduce([wallet.state, []], ([state, removed], { spend, spendTime }) => [
72
+ state.processTtls(DateOps.addSeconds(spendTime, wallet.state.params.dustGracePeriodSeconds)),
73
+ Arr.append(removed, spend.oldNullifier),
74
+ ]));
75
+ return {
76
+ ...wallet,
77
+ state: updatedState,
78
+ pendingDust: wallet.pendingDust.filter((coin) => !removedNullifiers.includes(coin.nullifier)),
79
+ };
80
+ },
81
+ revertTransaction(wallet, tx) {
82
+ return CoreWallet.applyFailed(wallet, tx);
83
+ },
84
+ updateProgress(wallet, { appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }) {
85
+ const updatedProgress = SyncProgress.createSyncProgress({
86
+ appliedIndex: appliedIndex ?? wallet.progress.appliedIndex,
87
+ highestRelevantWalletIndex: highestRelevantWalletIndex ?? wallet.progress.highestRelevantWalletIndex,
88
+ highestIndex: highestIndex ?? wallet.progress.highestIndex,
89
+ highestRelevantIndex: highestRelevantIndex ?? wallet.progress.highestRelevantIndex,
90
+ isConnected: isConnected ?? wallet.progress.isConnected,
91
+ });
92
+ return { ...wallet, progress: updatedProgress };
93
+ },
94
+ spendCoins(wallet, secretKey, coins, currentTime) {
95
+ const [output, newState, newPending] = pipe(coins, Arr.reduce([[], wallet.state, wallet.pendingDust], ([spends, localState, pending], { token: coinToSpend, value: takeFee }) => {
96
+ const [newState, dustSpend] = localState.spend(secretKey, coinToSpend, takeFee, currentTime);
97
+ const newPending = [...pending, { ...coinToSpend, nullifier: dustSpend.oldNullifier }];
98
+ return [Arr.append(spends, dustSpend), newState, newPending];
99
+ }));
100
+ return [output, { ...wallet, state: newState, pendingDust: newPending }];
101
+ },
102
+ pendingDustToMap(coins) {
103
+ return new Map(coins.map(({ nullifier, ...coins }) => [nullifier, coins]));
104
+ },
105
+ };
@@ -0,0 +1,8 @@
1
+ import { type DustPublicKey } from '@midnight-ntwrk/ledger-v8';
2
+ import { DustAddress } from '@midnightntwrk/wallet-sdk-address-format';
3
+ import { type CoreWallet } from './CoreWallet.js';
4
+ export type KeysCapability<TState> = {
5
+ getPublicKey(state: TState): DustPublicKey;
6
+ getAddress(state: TState): DustAddress;
7
+ };
8
+ export declare const makeDefaultKeysCapability: () => KeysCapability<CoreWallet>;
@@ -0,0 +1,11 @@
1
+ import { DustAddress } from '@midnightntwrk/wallet-sdk-address-format';
2
+ export const makeDefaultKeysCapability = () => {
3
+ return {
4
+ getPublicKey: (state) => {
5
+ return state.publicKey.publicKey;
6
+ },
7
+ getAddress: (state) => {
8
+ return new DustAddress(state.publicKey.publicKey);
9
+ },
10
+ };
11
+ };
@@ -0,0 +1,47 @@
1
+ import { Effect, Stream, Scope } from 'effect';
2
+ import { type TransactionHistoryService } from './TransactionHistory.js';
3
+ import { type DustSecretKey, type Signature, type SignatureVerifyingKey, type FinalizedTransaction, type UnprovenTransaction } from '@midnight-ntwrk/ledger-v8';
4
+ import { type WalletError } from './WalletError.js';
5
+ import { type WalletRuntimeError, type Variant, StateChange } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
6
+ import { type UtxoWithMeta } from './types/Dust.js';
7
+ import { type KeysCapability } from './Keys.js';
8
+ import { type ChangesResult, type SyncCapability, type SyncService } from './Sync.js';
9
+ import { type SimulatorState } from '@midnightntwrk/wallet-sdk-capabilities/simulation';
10
+ import { type CoinsAndBalancesCapability, type CoinSelection } from './CoinsAndBalances.js';
11
+ import { type NightUtxoSplitForDustRegistration, type TransactingCapability } from './Transacting.js';
12
+ import { type CoreWallet } from './CoreWallet.js';
13
+ import { type SerializationCapability } from './Serialization.js';
14
+ import { type AnyTransaction } from './types/ledger.js';
15
+ import { type DustAddress } from '@midnightntwrk/wallet-sdk-address-format';
16
+ export declare namespace RunningV1Variant {
17
+ type Context<TSerialized, TSyncUpdate, TTransaction, TStartAux> = {
18
+ serializationCapability: SerializationCapability<CoreWallet, null, TSerialized>;
19
+ syncService: SyncService<CoreWallet, TStartAux, TSyncUpdate>;
20
+ syncCapability: SyncCapability<CoreWallet, TSyncUpdate, ChangesResult>;
21
+ transactingCapability: TransactingCapability<DustSecretKey, CoreWallet, TTransaction>;
22
+ coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
23
+ keysCapability: KeysCapability<CoreWallet>;
24
+ coinSelection: CoinSelection;
25
+ transactionHistoryService: TransactionHistoryService;
26
+ };
27
+ type AnyContext = Context<any, any, any, any>;
28
+ }
29
+ export declare const V1Tag: unique symbol;
30
+ export type DefaultRunningV1 = RunningV1Variant<string, SimulatorState, FinalizedTransaction, DustSecretKey>;
31
+ export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux> implements Variant.RunningVariant<typeof V1Tag, CoreWallet> {
32
+ #private;
33
+ __polyTag__: typeof V1Tag;
34
+ readonly state: Stream.Stream<StateChange.StateChange<CoreWallet>, WalletRuntimeError>;
35
+ constructor(scope: Scope.Scope, context: Variant.VariantContext<CoreWallet>, v1Context: RunningV1Variant.Context<TSerialized, TSyncUpdate, TTransaction, TStartAux>);
36
+ startSyncInBackground(startAux: TStartAux): Effect.Effect<void>;
37
+ startSync(startAux: TStartAux): Stream.Stream<void, WalletError, Scope.Scope>;
38
+ createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Effect.Effect<UnprovenTransaction, WalletError>;
39
+ splitNightUtxosForDustRegistration(currentTime: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, isRegistration: boolean): Effect.Effect<NightUtxoSplitForDustRegistration, WalletError>;
40
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Effect.Effect<UnprovenTransaction, WalletError>;
41
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
42
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
43
+ calculateFee(transactions: ReadonlyArray<AnyTransaction>): Effect.Effect<bigint, WalletError>;
44
+ estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<bigint, WalletError>;
45
+ balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<UnprovenTransaction, WalletError>;
46
+ revertTransaction(transaction: AnyTransaction): Effect.Effect<void, WalletError>;
47
+ }
@@ -0,0 +1,143 @@
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 { Effect, SubscriptionRef, Stream, pipe, Scope, Sink, Console, Duration, Schedule, Array as Arr } from 'effect';
14
+ import { nativeToken, } from '@midnight-ntwrk/ledger-v8';
15
+ import { ProtocolVersion } from '@midnightntwrk/wallet-sdk-abstractions';
16
+ import { OtherWalletError } from './WalletError.js';
17
+ import { ArrayOps, EitherOps } from '@midnightntwrk/wallet-sdk-utilities';
18
+ import { StateChange, VersionChangeType, } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
19
+ const progress = (state) => {
20
+ const appliedIndex = state.progress?.appliedIndex ?? 0n;
21
+ const highestRelevantWalletIndex = state.progress?.highestRelevantWalletIndex ?? 0n;
22
+ const highestIndex = state.progress?.highestIndex ?? 0n;
23
+ const highestRelevantIndex = state.progress?.highestRelevantIndex ?? 0n;
24
+ const sourceGap = highestIndex - highestRelevantIndex;
25
+ const applyGap = highestRelevantWalletIndex - appliedIndex;
26
+ return [StateChange.ProgressUpdate({ sourceGap, applyGap })];
27
+ };
28
+ const protocolVersionChange = (previous, current) => {
29
+ return previous.protocolVersion != current.protocolVersion
30
+ ? [
31
+ StateChange.VersionChange({
32
+ change: VersionChangeType.Version({
33
+ version: ProtocolVersion.ProtocolVersion(current.protocolVersion),
34
+ }),
35
+ }),
36
+ ]
37
+ : [];
38
+ };
39
+ export const V1Tag = Symbol('V1');
40
+ export class RunningV1Variant {
41
+ __polyTag__ = V1Tag;
42
+ #scope;
43
+ #context;
44
+ #v1Context;
45
+ state;
46
+ constructor(scope, context, v1Context) {
47
+ this.#scope = scope;
48
+ this.#context = context;
49
+ this.#v1Context = v1Context;
50
+ this.state = Stream.fromEffect(context.stateRef.get).pipe(Stream.flatMap((initialState) => context.stateRef.changes.pipe(Stream.mapAccum(initialState, (previous, current) => {
51
+ return [current, [previous, current]];
52
+ }))), Stream.mapConcat(([previous, current]) => {
53
+ // TODO: emit progress only upon actual change
54
+ return [
55
+ StateChange.State({ state: current }),
56
+ ...progress(current),
57
+ ...protocolVersionChange(previous, current),
58
+ ];
59
+ }));
60
+ }
61
+ startSyncInBackground(startAux) {
62
+ return this.startSync(startAux).pipe(Stream.runScoped(Sink.drain), Effect.forkScoped, Effect.provideService(Scope.Scope, this.#scope));
63
+ }
64
+ startSync(startAux) {
65
+ return pipe(SubscriptionRef.get(this.#context.stateRef), Stream.fromEffect, Stream.flatMap((state) => this.#v1Context.syncService.updates(state, startAux)), Stream.mapEffect((update) => SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => Effect.try({
66
+ try: () => {
67
+ const [newState, changesResult] = this.#v1Context.syncCapability.applyUpdate(state, update);
68
+ return [changesResult, newState];
69
+ },
70
+ catch: (err) => new OtherWalletError({
71
+ message: 'Error while applying sync update',
72
+ cause: err,
73
+ }),
74
+ })).pipe(Effect.flatMap(({ changes, protocolVersion }) =>
75
+ // Skip the tx-history fork entirely when there are no changes.
76
+ // Forking unconditionally allocates a fiber per apply call (one
77
+ // for every batch the sync emits, even the all-progress ones),
78
+ // which adds up fast during catch-up.
79
+ changes.length === 0
80
+ ? Effect.void
81
+ : pipe(Effect.forEach(changes, (change) => pipe(this.#v1Context.transactionHistoryService.getTransactionDetails(change.source), Effect.flatMap((metadata) => this.#v1Context.transactionHistoryService.put(change, metadata, protocolVersion)), Effect.catchAllCause((cause) => Console.error('Error processing tx history metadata', cause))), { discard: true, concurrency: 'unbounded' }), Effect.forkScoped)), Effect.provideService(Scope.Scope, this.#scope))), Stream.tapError((error) => Console.error(error)), Stream.retry(pipe(Schedule.exponential(Duration.seconds(1), 2), Schedule.map((delay) => {
82
+ const maxDelay = Duration.minutes(2);
83
+ const jitter = Duration.millis(Math.floor(Math.random() * 1000));
84
+ const delayWithJitter = Duration.toMillis(delay) + Duration.toMillis(jitter);
85
+ return Duration.millis(Math.min(delayWithJitter, Duration.toMillis(maxDelay)));
86
+ }))));
87
+ }
88
+ createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress) {
89
+ if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
90
+ return Effect.fail(new OtherWalletError({ message: 'Token of a non-Night type received' }));
91
+ }
92
+ return Effect.gen(this, function* () {
93
+ const currentState = yield* SubscriptionRef.get(this.#context.stateRef);
94
+ const blockData = yield* this.#v1Context.syncService.blockData();
95
+ const resolvedTime = currentTime ?? blockData.timestamp;
96
+ const utxosWithDustValue = this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, resolvedTime);
97
+ return yield* this.#v1Context.transactingCapability
98
+ .createDustGenerationTransaction(resolvedTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
99
+ .pipe(EitherOps.toEffect);
100
+ });
101
+ }
102
+ splitNightUtxosForDustRegistration(currentTime, nightUtxos, isRegistration) {
103
+ if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
104
+ return Effect.fail(new OtherWalletError({ message: 'Token of a non-Night type received' }));
105
+ }
106
+ return Effect.gen(this, function* () {
107
+ const currentState = yield* SubscriptionRef.get(this.#context.stateRef);
108
+ const utxosWithDustValue = this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, currentTime);
109
+ return this.#v1Context.transactingCapability.splitNightUtxosForDustRegistration(utxosWithDustValue, isRegistration);
110
+ });
111
+ }
112
+ attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment) {
113
+ return this.#v1Context.transactingCapability
114
+ .attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment)
115
+ .pipe(EitherOps.toEffect);
116
+ }
117
+ addDustGenerationSignature(transaction, signature) {
118
+ return this.#v1Context.transactingCapability
119
+ .addDustGenerationSignature(transaction, signature)
120
+ .pipe(EitherOps.toEffect);
121
+ }
122
+ addDustRegistrationSignature(transaction, signature) {
123
+ return this.#v1Context.transactingCapability
124
+ .addDustRegistrationSignature(transaction, signature)
125
+ .pipe(EitherOps.toEffect);
126
+ }
127
+ calculateFee(transactions) {
128
+ return pipe(this.#v1Context.syncService.blockData(), Effect.map((blockData) => pipe(transactions, Arr.map((transaction) => this.#v1Context.transactingCapability.calculateFee(transaction, blockData.ledgerParameters)), ArrayOps.sumBigInt)));
129
+ }
130
+ estimateFee(secretKey, transactions, ttl, currentTime) {
131
+ return pipe(Effect.all([SubscriptionRef.get(this.#context.stateRef), this.#v1Context.syncService.blockData()]), Effect.flatMap(([state, blockData]) => pipe(this.#v1Context.transactingCapability.estimateFee(secretKey, state, transactions, ttl, currentTime ?? blockData.timestamp, blockData.ledgerParameters), EitherOps.toEffect)));
132
+ }
133
+ balanceTransactions(secretKey, transactions, ttl, currentTime) {
134
+ return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
135
+ return pipe(this.#v1Context.syncService.blockData(), Effect.flatMap((blockData) => this.#v1Context.transactingCapability.balanceTransactions(secretKey, state, transactions, ttl, currentTime ?? blockData.timestamp, blockData.ledgerParameters)));
136
+ });
137
+ }
138
+ revertTransaction(transaction) {
139
+ return SubscriptionRef.updateEffect(this.#context.stateRef, (state) => {
140
+ return pipe(this.#v1Context.transactingCapability.revertTransaction(state, transaction), EitherOps.toEffect);
141
+ });
142
+ }
143
+ }
@@ -0,0 +1,9 @@
1
+ import { Either, Schema } from 'effect';
2
+ import { type WalletError } from './WalletError.js';
3
+ import { CoreWallet } from './CoreWallet.js';
4
+ export type SerializationCapability<TWallet, TAux, TSerialized> = {
5
+ serialize(wallet: TWallet): TSerialized;
6
+ deserialize(aux: TAux, data: TSerialized): Either.Either<TWallet, WalletError>;
7
+ };
8
+ export declare const Uint8ArraySchema: Schema.declare<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, readonly [], never>;
9
+ export declare const makeDefaultV1SerializationCapability: () => SerializationCapability<CoreWallet, null, string>;
@@ -0,0 +1,75 @@
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 { Effect, ParseResult, Either, pipe, Schema } from 'effect';
14
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
15
+ import { OtherWalletError } from './WalletError.js';
16
+ import { CoreWallet } from './CoreWallet.js';
17
+ const StateSchema = Schema.declare((input) => input instanceof ledger.DustLocalState).annotations({
18
+ identifier: 'ledger.DustLocalState',
19
+ });
20
+ export const Uint8ArraySchema = Schema.declare((input) => input instanceof Uint8Array).annotations({
21
+ identifier: 'Uint8Array',
22
+ });
23
+ const StateFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, StateSchema, {
24
+ encode: (state) => {
25
+ return Effect.try({
26
+ try: () => {
27
+ return state.serialize();
28
+ },
29
+ catch: (err) => {
30
+ return new ParseResult.Unexpected(err, 'Could not serialize local state');
31
+ },
32
+ });
33
+ },
34
+ decode: (bytes) => Effect.try({
35
+ try: () => ledger.DustLocalState.deserialize(bytes),
36
+ catch: (err) => {
37
+ return new ParseResult.Unexpected(err, 'Could not deserialize local state');
38
+ },
39
+ }),
40
+ }));
41
+ const HexedState = pipe(Schema.Uint8ArrayFromHex, Schema.compose(StateFromUInt8Array));
42
+ const SnapshotSchema = Schema.Struct({
43
+ publicKey: Schema.Struct({
44
+ publicKey: Schema.BigInt,
45
+ }),
46
+ state: HexedState,
47
+ protocolVersion: Schema.BigInt,
48
+ networkId: Schema.String,
49
+ offset: Schema.optional(Schema.BigInt),
50
+ });
51
+ export const makeDefaultV1SerializationCapability = () => {
52
+ return {
53
+ serialize: (wallet) => {
54
+ const buildSnapshot = (w) => ({
55
+ publicKey: w.publicKey,
56
+ state: w.state,
57
+ protocolVersion: w.protocolVersion,
58
+ networkId: w.networkId,
59
+ offset: w.progress?.appliedIndex,
60
+ });
61
+ return pipe(wallet, buildSnapshot, Schema.encodeSync(SnapshotSchema), JSON.stringify);
62
+ },
63
+ deserialize: (aux, serialized) => {
64
+ return pipe(serialized, Schema.decodeUnknownEither(Schema.parseJson(SnapshotSchema)), Either.mapLeft((err) => new OtherWalletError({ message: 'Error while deserializing snapshot', cause: err })), Either.flatMap((snapshot) => Either.try({
65
+ try: () => CoreWallet.restore(snapshot.state, snapshot.publicKey, [], {
66
+ appliedIndex: snapshot.offset ?? 0n,
67
+ highestRelevantWalletIndex: 0n,
68
+ highestIndex: 0n,
69
+ highestRelevantIndex: 0n,
70
+ }, snapshot.protocolVersion, snapshot.networkId),
71
+ catch: (err) => new OtherWalletError({ message: 'Error while restoring core wallet', cause: err }),
72
+ })));
73
+ },
74
+ };
75
+ };
@@ -0,0 +1,96 @@
1
+ import { Effect, Layer, Schema, type Scope, Stream } from 'effect';
2
+ import { type DustSecretKey, type DustStateChanges, Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v8';
3
+ import { type SubscriptionClient, type QueryClient } from '@midnightntwrk/wallet-sdk-indexer-client/effect';
4
+ import { type WalletError } from './WalletError.js';
5
+ import { type Simulator, type SimulatorState } from '@midnightntwrk/wallet-sdk-capabilities/simulation';
6
+ import { CoreWallet } from './CoreWallet.js';
7
+ import { type NetworkId } from './types/ledger.js';
8
+ export interface SyncService<TState, TStartAux, TUpdate> {
9
+ updates: (state: TState, auxData: TStartAux) => Stream.Stream<TUpdate, WalletError, Scope.Scope>;
10
+ blockData: () => Effect.Effect<BlockData, WalletError>;
11
+ }
12
+ export interface BlockData {
13
+ hash: string;
14
+ height: number;
15
+ ledgerParameters: LedgerParameters;
16
+ timestamp: Date;
17
+ }
18
+ export type ChangesResult = {
19
+ readonly changes: DustStateChanges[];
20
+ readonly protocolVersion: number;
21
+ };
22
+ export interface SyncCapability<TState, TUpdate, TResult> {
23
+ applyUpdate: (state: TState, update: TUpdate) => [TState, TResult];
24
+ }
25
+ export type IndexerClientConnection = {
26
+ indexerHttpUrl: string;
27
+ indexerWsUrl?: string;
28
+ keepAlive?: number;
29
+ /** Cap on the in-flight event queue between the WebSocket push and the apply loop. Default: 10000. */
30
+ bufferSize?: number;
31
+ /** In-flight count at which the disposed WS subscription is reopened. Default: 100. */
32
+ resumeThreshold?: number;
33
+ };
34
+ export type BatchUpdatesConfig = {
35
+ /**
36
+ * Maximum number of events to collect into a single batch before emitting.
37
+ *
38
+ * @default 10
39
+ */
40
+ readonly size?: number;
41
+ /**
42
+ * Maximum time in milliseconds to wait for a full batch before emitting a partial one. Controls the `groupedWithin`
43
+ * timeout — lower values mean more responsive (but smaller) batches when events arrive slowly.
44
+ *
45
+ * @default 1
46
+ */
47
+ readonly timeout?: number;
48
+ /**
49
+ * Minimum delay in milliseconds injected between consecutive batches. Prevents the sync stream from saturating
50
+ * downstream consumers when many events are available at once. Set to 0 to disable spacing entirely.
51
+ *
52
+ * @default 4
53
+ */
54
+ readonly spacing?: number;
55
+ };
56
+ export type DefaultSyncConfiguration = {
57
+ indexerClientConnection: IndexerClientConnection;
58
+ networkId: NetworkId;
59
+ batchUpdates?: BatchUpdatesConfig;
60
+ };
61
+ export type SimulatorSyncConfiguration = {
62
+ simulator: Simulator;
63
+ networkId: NetworkId;
64
+ };
65
+ export type SimulatorSyncUpdate = {
66
+ update: SimulatorState;
67
+ secretKey: DustSecretKey;
68
+ };
69
+ export type SecretKeysResource = <A>(cb: (key: DustSecretKey) => A) => A;
70
+ export declare const SecretKeysResource: {
71
+ create: (secretKey: DustSecretKey) => SecretKeysResource;
72
+ };
73
+ export declare const SyncEventsUpdateSchema: Schema.Struct<{
74
+ id: typeof Schema.Number;
75
+ raw: Schema.Schema<LedgerEvent, string, never>;
76
+ maxId: typeof Schema.Number;
77
+ }>;
78
+ export type WalletSyncSubscription = Schema.Schema.Type<typeof SyncEventsUpdateSchema>;
79
+ export type WalletSyncUpdate = {
80
+ updates: WalletSyncSubscription[];
81
+ secretKey: DustSecretKey;
82
+ timestamp: Date;
83
+ };
84
+ export declare const WalletSyncUpdate: {
85
+ create: (updates: WalletSyncSubscription[], secretKey: DustSecretKey, timestamp: Date) => WalletSyncUpdate;
86
+ };
87
+ export declare const makeDefaultSyncService: (config: DefaultSyncConfiguration) => SyncService<CoreWallet, DustSecretKey, WalletSyncUpdate>;
88
+ export type IndexerSyncService = {
89
+ connectionLayer: () => Layer.Layer<SubscriptionClient, WalletError, Scope.Scope>;
90
+ subscribeWallet: (state: CoreWallet) => Stream.Stream<WalletSyncSubscription, WalletError, Scope.Scope | SubscriptionClient>;
91
+ queryClient: () => Layer.Layer<QueryClient, WalletError, Scope.Scope>;
92
+ };
93
+ export declare const makeIndexerSyncService: (config: DefaultSyncConfiguration) => IndexerSyncService;
94
+ export declare const makeDefaultSyncCapability: () => SyncCapability<CoreWallet, WalletSyncUpdate, ChangesResult>;
95
+ export declare const makeSimulatorSyncService: (config: SimulatorSyncConfiguration) => SyncService<CoreWallet, DustSecretKey, SimulatorSyncUpdate>;
96
+ export declare const makeSimulatorSyncCapability: () => SyncCapability<CoreWallet, SimulatorSyncUpdate, ChangesResult>;