@midnight-ntwrk/wallet-sdk-dust-wallet 4.0.0 → 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.
package/README.md CHANGED
@@ -1,3 +1,10 @@
1
+ > [!IMPORTANT]
2
+ > **This package has moved.** The `@midnight-ntwrk` scope is published only
3
+ > during the migration window and will stop receiving updates. Please migrate to
4
+ > [`@midnightntwrk/wallet-sdk-dust-wallet`](https://www.npmjs.com/package/@midnightntwrk/wallet-sdk-dust-wallet).
5
+
6
+ ---
7
+
1
8
  # @midnight-ntwrk/wallet-sdk-dust-wallet
2
9
 
3
10
  Manages dust (transaction fees) on the Midnight network.
@@ -2,11 +2,13 @@ import { type DustParameters, type DustPublicKey, DustSecretKey, type FinalizedT
2
2
  import { type ProtocolState, ProtocolVersion, type SyncProgress } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
3
  import { type DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
4
4
  import { type Variant, type VariantBuilder, type WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
5
+ import { type Clock } from '@midnight-ntwrk/wallet-sdk-utilities';
5
6
  import * as rx from 'rxjs';
6
7
  import { type Balance, type CoinsAndBalancesCapability, type UtxoWithFullDustDetails } from './v1/CoinsAndBalances.js';
7
8
  import { CoreWallet } from './v1/CoreWallet.js';
8
9
  import { type KeysCapability } from './v1/Keys.js';
9
10
  import { type SerializationCapability } from './v1/Serialization.js';
11
+ import { type NightUtxoSplitForDustRegistration } from './v1/Transacting.js';
10
12
  import { type DustFullInfo, type UtxoWithMeta } from './v1/types/Dust.js';
11
13
  import { type AnyTransaction } from './v1/types/ledger.js';
12
14
  import { type BaseV1Configuration, type DefaultV1Configuration, type V1Variant } from './v1/V1Builder.js';
@@ -41,12 +43,44 @@ export type DustWalletAPI<TStartAux = DustSecretKey, TSerialized = string> = {
41
43
  readonly state: rx.Observable<DustWalletState<TSerialized>>;
42
44
  start(secretKey: TStartAux): Promise<void>;
43
45
  createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: Array<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Promise<UnprovenTransaction>;
46
+ splitNightUtxosForDustRegistration(currentTime: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, isRegistration: boolean): Promise<NightUtxoSplitForDustRegistration>;
47
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Promise<UnprovenTransaction>;
44
48
  addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<UnprovenTransaction>;
49
+ /**
50
+ * Attaches a signature to the DustRegistration in segment 1's `dustActions` only. Unlike
51
+ * {@link addDustGenerationSignature}, this does NOT touch the unshielded offers — those should be signed separately
52
+ * via the unshielded-wallet signing path. Use this when the caller orchestrates signing across both packages (e.g.
53
+ * the facade's `signRecipe`).
54
+ */
55
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<UnprovenTransaction>;
45
56
  calculateFee(transactions: ReadonlyArray<AnyTransaction>): Promise<bigint>;
46
57
  estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl?: Date, currentTime?: Date): Promise<bigint>;
47
58
  balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Promise<UnprovenTransaction>;
48
59
  serializeState(): Promise<TSerialized>;
49
60
  waitForSyncedState(allowedGap?: bigint): Promise<DustWalletState<TSerialized>>;
61
+ /**
62
+ * Resolves when the dust projected to be generated by the single highest-generation unregistered Night UTxO reaches
63
+ * `requiredAmount`. The projection is re-evaluated every second so the wait advances even when the dust state stream
64
+ * is quiet. Tracks the same quantity used as `allow_fee_payment` for the registration (the maximum across the UTxOs,
65
+ * not their sum, since `splitNightUtxos` puts only one UTxO in the guaranteed slot), so pairing with
66
+ * `WalletFacade.estimateRegistration` to pick `requiredAmount` guarantees the subsequent
67
+ * `registerNightUtxosForDustGeneration` will pass its fee-coverage guard.
68
+ *
69
+ * @param nightUtxos - UTxOs to project generation for; same set passed to `registerNightUtxosForDustGeneration`.
70
+ * Already-registered UTxOs are ignored. Must be non-empty.
71
+ * @param requiredAmount - Threshold to wait for, as a Dust amount. Resolves immediately if `<= 0n`.
72
+ * @param clock - Source of current time, read on every tick. Required, and a {@link Clock.Clock} rather than a
73
+ * snapshot `Date` like the other methods' `currentTime`: the projection only advances because the time is re-read
74
+ * each tick, and callers must inject their own clock so simulator-driven tests respect simulator time.
75
+ * @param opts.timeoutMs - Deadline, in ms from subscription, for `requiredAmount` to be reached; rejects if it is
76
+ * not. Default `300_000`.
77
+ * @returns A promise that resolves once the projected dust reaches `requiredAmount`.
78
+ * @throws Error if `nightUtxos` is empty.
79
+ * @throws TimeoutError if `requiredAmount` is not reached within `opts.timeoutMs`.
80
+ */
81
+ waitForGeneratedDust(nightUtxos: ReadonlyArray<UtxoWithMeta>, requiredAmount: bigint, clock: Clock.Clock, opts?: {
82
+ timeoutMs?: number;
83
+ }): Promise<void>;
50
84
  revertTransaction(transaction: AnyTransaction): Promise<void>;
51
85
  getAddress(): Promise<DustAddress>;
52
86
  stop(): Promise<void>;
@@ -98,6 +98,20 @@ export function CustomDustWallet(configuration, builder) {
98
98
  })
99
99
  .pipe(Effect.runPromise);
100
100
  }
101
+ async splitNightUtxosForDustRegistration(currentTime, nightUtxos, isRegistration) {
102
+ return this.runtime
103
+ .dispatch({
104
+ [V1Tag]: (v1) => v1.splitNightUtxosForDustRegistration(currentTime, nightUtxos, isRegistration),
105
+ })
106
+ .pipe(Effect.runPromise);
107
+ }
108
+ async attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment) {
109
+ return this.runtime
110
+ .dispatch({
111
+ [V1Tag]: (v1) => v1.attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment),
112
+ })
113
+ .pipe(Effect.runPromise);
114
+ }
101
115
  addDustGenerationSignature(transaction, signature) {
102
116
  return this.runtime
103
117
  .dispatch({
@@ -105,6 +119,13 @@ export function CustomDustWallet(configuration, builder) {
105
119
  })
106
120
  .pipe(Effect.runPromise);
107
121
  }
122
+ addDustRegistrationSignature(transaction, signature) {
123
+ return this.runtime
124
+ .dispatch({
125
+ [V1Tag]: (v1) => v1.addDustRegistrationSignature(transaction, signature),
126
+ })
127
+ .pipe(Effect.runPromise);
128
+ }
108
129
  calculateFee(transactions) {
109
130
  return this.runtime
110
131
  .dispatch({
@@ -137,6 +158,30 @@ export function CustomDustWallet(configuration, builder) {
137
158
  waitForSyncedState(allowedGap = 0n) {
138
159
  return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
139
160
  }
161
+ async waitForGeneratedDust(nightUtxos, requiredAmount, clock, opts) {
162
+ if (nightUtxos.length === 0) {
163
+ throw Error('At least one Night UTXO is required.');
164
+ }
165
+ if (requiredAmount <= 0n) {
166
+ return;
167
+ }
168
+ const timeoutMs = opts?.timeoutMs ?? 300_000;
169
+ // Combine the dust state stream with a 1 s tick — the dust state only emits when sync
170
+ // updates apply, but the generation projection depends on a current-time reading, which
171
+ // advances continuously. Without a periodic tick the filter would never re-run between
172
+ // state emissions on a quiet wallet, and the wait would hang.
173
+ await rx.firstValueFrom(rx.combineLatest([this.state, rx.timer(0, 1000)]).pipe(rx.filter(([dustState]) => {
174
+ // The registration's allow_fee_payment is capped at the single highest-generation
175
+ // unregistered Night UTxO (splitNightUtxos puts only 1 UTxO in the guaranteed slot),
176
+ // so the wait must track that same quantity — summing across UTxOs would resolve
177
+ // optimistically and the registration would still fail the fee check.
178
+ const maxGeneratedNow = dustState
179
+ .estimateDustGeneration(nightUtxos, clock.now())
180
+ .filter((u) => !u.utxo.registeredForDustGeneration)
181
+ .reduce((max, u) => (u.dust.generatedNow > max ? u.dust.generatedNow : max), 0n);
182
+ return maxGeneratedNow >= requiredAmount;
183
+ }), rx.timeout({ first: timeoutMs })));
184
+ }
140
185
  serializeState() {
141
186
  return rx.firstValueFrom(this.state).then((state) => state.serialize());
142
187
  }
@@ -1,3 +1,4 @@
1
+ import { type CoinRecipe } from '@midnight-ntwrk/wallet-sdk-capabilities';
1
2
  import { type CoreWallet } from './CoreWallet.js';
2
3
  import { type KeysCapability } from './Keys.js';
3
4
  import { type DustGenerationDetails, type DustGenerationInfo, type Dust, type DustFullInfo, type UtxoWithMeta } from './types/Dust.js';
@@ -14,8 +15,8 @@ export type UtxoWithFullDustDetails = Readonly<{
14
15
  utxo: UtxoWithMeta;
15
16
  dust: DustGenerationDetails;
16
17
  }>;
17
- export type CoinSelection<TInput> = (coins: readonly CoinWithValue<TInput>[]) => CoinWithValue<TInput> | undefined;
18
- export declare const chooseCoin: <TInput>(coins: readonly CoinWithValue<TInput>[]) => CoinWithValue<TInput> | undefined;
18
+ export type CoinSelection = <TCoin extends CoinRecipe>(coins: readonly TCoin[]) => TCoin | undefined;
19
+ export declare const chooseCoin: CoinSelection;
19
20
  export type CoinsAndBalancesCapability<TState> = {
20
21
  getWalletBalance(state: TState, time: Date): Balance;
21
22
  getAvailableCoins(state: TState, time?: Date): readonly DustFullInfo[];
@@ -13,7 +13,10 @@
13
13
  import * as ledger from '@midnight-ntwrk/ledger-v8';
14
14
  import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
15
15
  import { pipe, Array as Arr, Order } from 'effect';
16
- export const chooseCoin = (coins) => coins.toSorted((a, b) => Number(a.value - b.value)).at(0);
16
+ export const chooseCoin = (coins) => coins
17
+ .filter((coin) => coin.value > 0n)
18
+ .toSorted((a, b) => Number(a.value - b.value))
19
+ .at(0);
17
20
  const FAKE_NONCE = '0'.repeat(64);
18
21
  export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
19
22
  const getWalletBalance = (state, time) => {
@@ -3,12 +3,12 @@ import { type TransactionHistoryService } from './TransactionHistory.js';
3
3
  import { type DustSecretKey, type Signature, type SignatureVerifyingKey, type FinalizedTransaction, type UnprovenTransaction } from '@midnight-ntwrk/ledger-v8';
4
4
  import { type WalletError } from './WalletError.js';
5
5
  import { type WalletRuntimeError, type Variant, StateChange } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
6
- import { type Dust, type UtxoWithMeta } from './types/Dust.js';
6
+ import { type UtxoWithMeta } from './types/Dust.js';
7
7
  import { type KeysCapability } from './Keys.js';
8
8
  import { type ChangesResult, type SyncCapability, type SyncService } from './Sync.js';
9
9
  import { type SimulatorState } from '@midnight-ntwrk/wallet-sdk-capabilities/simulation';
10
10
  import { type CoinsAndBalancesCapability, type CoinSelection } from './CoinsAndBalances.js';
11
- import { type TransactingCapability } from './Transacting.js';
11
+ import { type NightUtxoSplitForDustRegistration, type TransactingCapability } from './Transacting.js';
12
12
  import { type CoreWallet } from './CoreWallet.js';
13
13
  import { type SerializationCapability } from './Serialization.js';
14
14
  import { type AnyTransaction } from './types/ledger.js';
@@ -21,7 +21,7 @@ export declare namespace RunningV1Variant {
21
21
  transactingCapability: TransactingCapability<DustSecretKey, CoreWallet, TTransaction>;
22
22
  coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
23
23
  keysCapability: KeysCapability<CoreWallet>;
24
- coinSelection: CoinSelection<Dust>;
24
+ coinSelection: CoinSelection;
25
25
  transactionHistoryService: TransactionHistoryService;
26
26
  };
27
27
  type AnyContext = Context<any, any, any, any>;
@@ -36,7 +36,10 @@ export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction, TS
36
36
  startSyncInBackground(startAux: TStartAux): Effect.Effect<void>;
37
37
  startSync(startAux: TStartAux): Stream.Stream<void, WalletError, Scope.Scope>;
38
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>;
39
41
  addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
42
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
40
43
  calculateFee(transactions: ReadonlyArray<AnyTransaction>): Effect.Effect<bigint, WalletError>;
41
44
  estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<bigint, WalletError>;
42
45
  balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<UnprovenTransaction, WalletError>;
@@ -71,7 +71,14 @@ export class RunningV1Variant {
71
71
  message: 'Error while applying sync update',
72
72
  cause: err,
73
73
  }),
74
- })).pipe(Effect.flatMap(({ changes, protocolVersion }) => 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) => {
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) => {
75
82
  const maxDelay = Duration.minutes(2);
76
83
  const jitter = Duration.millis(Math.floor(Math.random() * 1000));
77
84
  const delayWithJitter = Duration.toMillis(delay) + Duration.toMillis(jitter);
@@ -82,19 +89,41 @@ export class RunningV1Variant {
82
89
  if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
83
90
  return Effect.fail(new OtherWalletError({ message: 'Token of a non-Night type received' }));
84
91
  }
85
- return Effect.Do.pipe(Effect.bind('currentState', () => SubscriptionRef.get(this.#context.stateRef)), Effect.bind('blockData', () => this.#v1Context.syncService.blockData()), Effect.let('currentTime', ({ blockData }) => currentTime ?? blockData.timestamp), Effect.let('utxosWithDustValue', ({ currentState, currentTime }) => {
86
- return this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, currentTime);
87
- }), Effect.flatMap(({ utxosWithDustValue, currentTime }) => {
88
- return this.#v1Context.transactingCapability
89
- .createDustGenerationTransaction(currentTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
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)
90
99
  .pipe(EitherOps.toEffect);
91
- }));
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);
92
116
  }
93
117
  addDustGenerationSignature(transaction, signature) {
94
118
  return this.#v1Context.transactingCapability
95
119
  .addDustGenerationSignature(transaction, signature)
96
120
  .pipe(EitherOps.toEffect);
97
121
  }
122
+ addDustRegistrationSignature(transaction, signature) {
123
+ return this.#v1Context.transactingCapability
124
+ .addDustRegistrationSignature(transaction, signature)
125
+ .pipe(EitherOps.toEffect);
126
+ }
98
127
  calculateFee(transactions) {
99
128
  return pipe(this.#v1Context.syncService.blockData(), Effect.map((blockData) => pipe(transactions, Arr.map((transaction) => this.#v1Context.transactingCapability.calculateFee(transaction, blockData.ledgerParameters)), ArrayOps.sumBigInt)));
100
129
  }
package/dist/v1/Sync.d.ts CHANGED
@@ -26,6 +26,10 @@ export type IndexerClientConnection = {
26
26
  indexerHttpUrl: string;
27
27
  indexerWsUrl?: string;
28
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;
29
33
  };
30
34
  export type BatchUpdatesConfig = {
31
35
  /**
package/dist/v1/Sync.js CHANGED
@@ -33,19 +33,13 @@ const LedgerEventSchema = Schema.declare((input) => input instanceof LedgerEvent
33
33
  identifier: 'ledger.Event',
34
34
  });
35
35
  const LedgerEventFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, LedgerEventSchema, {
36
- encode: (e) => {
37
- return Effect.try({
38
- try: () => e.serialize(),
39
- catch: (err) => {
40
- return new ParseResult.Unexpected(err, 'Could not serialize Ledger Event');
41
- },
42
- });
43
- },
36
+ encode: (e) => Effect.try({
37
+ try: () => e.serialize(),
38
+ catch: (err) => new ParseResult.Unexpected(err, 'Could not serialize Ledger Event'),
39
+ }),
44
40
  decode: (bytes) => Effect.try({
45
41
  try: () => LedgerEvent.deserialize(bytes),
46
- catch: (err) => {
47
- return new ParseResult.Unexpected(err, 'Could not deserialize Ledger Event');
48
- },
42
+ catch: (err) => new ParseResult.Unexpected(err, 'Could not deserialize Ledger Event'),
49
43
  }),
50
44
  }));
51
45
  const HexedEvent = pipe(Schema.Uint8ArrayFromHex, Schema.compose(LedgerEventFromUInt8Array));
@@ -110,8 +104,32 @@ export const makeIndexerSyncService = (config) => {
110
104
  },
111
105
  subscribeWallet(state) {
112
106
  const { appliedIndex } = state.progress;
113
- return pipe(DustLedgerEvents.run({
114
- id: Number(appliedIndex),
107
+ const bufferSize = config.indexerClientConnection.bufferSize ?? 10000;
108
+ const resumeThreshold = config.indexerClientConnection.resumeThreshold ?? 100;
109
+ // The boundary is load-bearing, not waste: this subscription emits only events (no tip/progress
110
+ // sentinel), and `isConnected`/the tip (`maxId`) are set only when an event is received. So the
111
+ // cursor must stay `<= appliedIndex` — never `appliedIndex + 1`. Requesting one event later would
112
+ // deliver nothing to a wallet already at the tip, so `applyUpdate` would never run and sync would
113
+ // hang.
114
+ //
115
+ // A fresh wallet has `appliedIndex === 0n` (the "nothing applied yet" sentinel), so `resumeFrom`
116
+ // is `-1n` and the `variables` mapping below opens the subscription with no `id` — the indexer
117
+ // streams from the very start. A restored wallet has `appliedIndex >= 1`, so `resumeFrom` is
118
+ // `appliedIndex - 1` and the inclusive cursor re-delivers the boundary event.
119
+ const resumeFrom = appliedIndex - 1n;
120
+ return pipe(
121
+ // Backpressure caps the in-flight queue between the WS push and the
122
+ // apply loop. Without it the JS heap grows linearly with catch-up
123
+ // depth, since `Stream.asyncPush({ bufferSize: 'unbounded' })`
124
+ // buffers every event the indexer pushes regardless of apply rate.
125
+ DustLedgerEvents.runWithBackpressure({
126
+ bufferSize,
127
+ resumeThreshold,
128
+ from: resumeFrom,
129
+ // `resumeFrom < 0n` means a fresh wallet: send no `id` so the indexer streams from the very
130
+ // start, rather than relying on `id: 0` sorting below the first real event id.
131
+ variables: (cursor) => ({ id: cursor < 0n ? null : Number(cursor) }),
132
+ key: (r) => BigInt(r.dustLedgerEvents.id),
115
133
  }), Stream.mapEffect((subscription) => pipe(Schema.decodeUnknownEither(SyncEventsUpdateSchema)(subscription.dustLedgerEvents), Either.mapLeft((err) => new SyncWalletError(err)), EitherOps.toEffect)), Stream.mapError((error) => new SyncWalletError(error)));
116
134
  },
117
135
  };
@@ -124,21 +142,14 @@ export const makeDefaultSyncCapability = () => {
124
142
  if (updates.length === 0) {
125
143
  return [state, { changes: [], protocolVersion: Number(state.protocolVersion) }];
126
144
  }
127
- const lastUpdate = updates.at(-1);
128
- const nextIndex = BigInt(lastUpdate.id);
129
- const highestRelevantWalletIndex = BigInt(lastUpdate.maxId);
130
- // in case the nextIndex is less than or equal to the current appliedIndex
131
- // just update highestRelevantWalletIndex
132
- if (nextIndex <= state.progress.appliedIndex) {
133
- return [
134
- CoreWallet.updateProgress(state, { highestRelevantWalletIndex, isConnected: true }),
135
- { changes: [], protocolVersion: Number(state.protocolVersion) },
136
- ];
137
- }
138
- const events = updates.map((u) => u.raw).filter((event) => event !== null);
139
- const [newState, changes] = CoreWallet.applyEventsWithChanges(state, secretKey, events, wrappedUpdate.timestamp);
145
+ const appliedIndex = state.progress.appliedIndex;
146
+ const freshUpdates = updates.filter((u) => BigInt(u.id) > appliedIndex);
147
+ const highestRelevantWalletIndex = BigInt(updates.at(-1).maxId);
148
+ const [newState, changes] = freshUpdates.length === 0
149
+ ? [state, []]
150
+ : CoreWallet.applyEventsWithChanges(state, secretKey, freshUpdates.map((u) => u.raw), wrappedUpdate.timestamp);
140
151
  const updatedState = CoreWallet.updateProgress(newState, {
141
- appliedIndex: nextIndex,
152
+ appliedIndex: freshUpdates.length === 0 ? appliedIndex : BigInt(freshUpdates.at(-1).id),
142
153
  highestRelevantWalletIndex,
143
154
  isConnected: true,
144
155
  });
@@ -6,11 +6,45 @@ import { CoreWallet } from './CoreWallet.js';
6
6
  import { type AnyTransaction, type Dust, type NetworkId, type TotalCostParameters } from './types/index.js';
7
7
  import { type CoinsAndBalancesCapability, type CoinSelection, type CoinWithValue, type UtxoWithFullDustDetails } from './CoinsAndBalances.js';
8
8
  import { type KeysCapability } from './Keys.js';
9
+ /**
10
+ * Result of splitting Night UTxOs into the guaranteed/fallible sections of a Dust registration or deregistration
11
+ * intent, together with the fee-payment allowance the registration may claim from dust generated by the guaranteed
12
+ * UTxO.
13
+ *
14
+ * For deregistration, `feePayment` is always `0n`.
15
+ */
16
+ export type NightUtxoSplitForDustRegistration = {
17
+ readonly guaranteedUtxos: ReadonlyArray<UtxoWithFullDustDetails>;
18
+ readonly fallibleUtxos: ReadonlyArray<UtxoWithFullDustDetails>;
19
+ readonly feePayment: bigint;
20
+ };
9
21
  export interface TransactingCapability<TSecrets, TState, _TTransaction> {
10
22
  readonly networkId: NetworkId;
11
23
  readonly costParams: TotalCostParameters;
12
24
  createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Either.Either<UnprovenTransaction, WalletError>;
25
+ /**
26
+ * Decides which Night UTxOs belong in the guaranteed vs fallible section of a dust registration/deregistration
27
+ * transaction, and computes the fee-payment allowance the registration may claim from dust generated by the
28
+ * guaranteed UTxO.
29
+ *
30
+ * For deregistration (`isRegistration === false`) the fee-payment allowance is always `0n`.
31
+ */
32
+ splitNightUtxosForDustRegistration(utxosWithDustValue: ReadonlyArray<UtxoWithFullDustDetails>, isRegistration: boolean): NightUtxoSplitForDustRegistration;
33
+ /**
34
+ * Attaches a Dust registration / deregistration action onto the intent (at segment 1) of an unproven transaction
35
+ * whose offers have already been built (typically by the unshielded wallet's `rotateUtxos`). The Night UTxOs are
36
+ * assumed to already appear in the intent's guaranteed/fallible offers and to already be booked in the unshielded
37
+ * wallet state.
38
+ */
39
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Either.Either<UnprovenTransaction, WalletError>;
13
40
  addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Either.Either<UnprovenTransaction, WalletError>;
41
+ /**
42
+ * Attaches a signature to the DustRegistration in segment 1's `dustActions` only. Unlike
43
+ * {@link addDustGenerationSignature}, this does NOT touch the unshielded offers — those are expected to be signed via
44
+ * the unshielded-wallet signing path. Use this when the caller orchestrates signing across both packages (e.g. the
45
+ * facade's `signRecipe`).
46
+ */
47
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Either.Either<UnprovenTransaction, WalletError>;
14
48
  calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
15
49
  estimateFee(secretKey: TSecrets, state: TState, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime: Date, ledgerParams: LedgerParameters): Either.Either<bigint, WalletError>;
16
50
  balanceTransactions(secretKey: TSecrets, state: TState, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime: Date, ledgerParams: LedgerParameters): Either.Either<[UnprovenTransaction, TState], WalletError>;
@@ -21,7 +55,7 @@ export type DefaultTransactingConfiguration = {
21
55
  costParameters: TotalCostParameters;
22
56
  };
23
57
  export type DefaultTransactingContext = {
24
- coinSelection: CoinSelection<Dust>;
58
+ coinSelection: CoinSelection;
25
59
  coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
26
60
  keysCapability: KeysCapability<CoreWallet>;
27
61
  };
@@ -41,11 +75,14 @@ export declare const findAvailableSegmentId: (transaction: AnyTransaction) => Op
41
75
  export declare class TransactingCapabilityImplementation<TTransaction extends AnyTransaction> implements TransactingCapability<DustSecretKey, CoreWallet, TTransaction> {
42
76
  readonly networkId: string;
43
77
  readonly costParams: TotalCostParameters;
44
- readonly getCoinSelection: () => CoinSelection<Dust>;
78
+ readonly getCoinSelection: () => CoinSelection;
45
79
  readonly getCoins: () => CoinsAndBalancesCapability<CoreWallet>;
46
80
  readonly getKeys: () => KeysCapability<CoreWallet>;
47
- constructor(networkId: NetworkId, costParams: TotalCostParameters, getCoinSelection: () => CoinSelection<Dust>, getCoins: () => CoinsAndBalancesCapability<CoreWallet>, getKeys: () => KeysCapability<CoreWallet>);
81
+ constructor(networkId: NetworkId, costParams: TotalCostParameters, getCoinSelection: () => CoinSelection, getCoins: () => CoinsAndBalancesCapability<CoreWallet>, getKeys: () => KeysCapability<CoreWallet>);
48
82
  createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Either.Either<UnprovenTransaction, WalletError>;
83
+ splitNightUtxosForDustRegistration(utxosWithDustValue: ReadonlyArray<UtxoWithFullDustDetails>, isRegistration: boolean): NightUtxoSplitForDustRegistration;
84
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Either.Either<UnprovenTransaction, WalletError>;
85
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signatureData: Signature): Either.Either<UnprovenTransaction, WalletError>;
49
86
  addDustGenerationSignature(transaction: UnprovenTransaction, signatureData: Signature): Either.Either<UnprovenTransaction, WalletError>;
50
87
  calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
51
88
  dryRunFee(recipeInputs: ReadonlyArray<CoinWithValue<Dust>>, transactions: ReadonlyArray<FinalizedTransaction | UnprovenTransaction>, secretKey: DustSecretKey, state: CoreWallet, ttl: Date, currentTime: Date, ledgerParams: LedgerParameters): bigint;
@@ -108,6 +108,72 @@ export class TransactingCapabilityImplementation {
108
108
  });
109
109
  });
110
110
  }
111
+ splitNightUtxosForDustRegistration(utxosWithDustValue, isRegistration) {
112
+ const splitResult = this.getCoins().splitNightUtxos(utxosWithDustValue);
113
+ // Deregistration must not claim dust as fee payment (matches the historical
114
+ // `dustReceiverAddress === undefined` branch of createDustGenerationTransaction).
115
+ const feePayment = isRegistration
116
+ ? pipe(splitResult.guaranteed, IterableOps.filter((coin) => !coin.utxo.registeredForDustGeneration), IterableOps.map((coin) => coin.dust.generatedNow), BigIntOps.sumAll)
117
+ : 0n;
118
+ return {
119
+ guaranteedUtxos: splitResult.guaranteed,
120
+ fallibleUtxos: splitResult.fallible,
121
+ feePayment,
122
+ };
123
+ }
124
+ attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment) {
125
+ return Either.gen(this, function* () {
126
+ const intent = transaction.intents?.get(1);
127
+ if (!intent) {
128
+ return yield* Either.left(new TransactingError({
129
+ message: 'No intent found at segment 1; expected an intent built by rotateUtxos',
130
+ }));
131
+ }
132
+ if (intent.dustActions !== undefined && intent.dustActions.registrations.length > 0) {
133
+ return yield* Either.left(new TransactingError({ message: 'Intent at segment 1 already has a dust registration attached' }));
134
+ }
135
+ return yield* LedgerOps.ledgerTry(() => {
136
+ const receiver = dustReceiverAddress ? dustReceiverAddress.data : undefined;
137
+ const dustRegistration = new DustRegistration(SignatureMarker.signature, nightVerifyingKey, receiver, feePayment);
138
+ const dustActions = new DustActions(SignatureMarker.signature, ProofMarker.preProof, currentTime, [], [dustRegistration]);
139
+ // Intents fetched from `transaction.intents.get(...)` behave like value copies — assigning
140
+ // to a field doesn't propagate back. Match the addDustGenerationSignature pattern: copy the
141
+ // intent via serialize/deserialize, mutate the copy, then set it back into a fresh
142
+ // transaction's intents map.
143
+ const newIntent = Intent.deserialize(SignatureMarker.signature, ProofMarker.preProof, BindingMarker.preBinding, intent.serialize());
144
+ newIntent.dustActions = dustActions;
145
+ const newTransaction = Transaction.deserialize(SignatureMarker.signature, ProofMarker.preProof, BindingMarker.preBinding, transaction.serialize());
146
+ newTransaction.intents = newTransaction.intents.set(1, newIntent);
147
+ return newTransaction;
148
+ });
149
+ });
150
+ }
151
+ addDustRegistrationSignature(transaction, signatureData) {
152
+ return Either.gen(this, function* () {
153
+ const intent = transaction.intents?.get(1);
154
+ if (!intent) {
155
+ return yield* Either.left(new TransactingError({ message: 'No intent found in the transaction intents with segment = 1' }));
156
+ }
157
+ const { dustActions } = intent;
158
+ if (!dustActions) {
159
+ return yield* Either.left(new TransactingError({ message: 'No dustActions found in intent' }));
160
+ }
161
+ const [registration, ...restRegistrations] = dustActions.registrations;
162
+ if (!registration) {
163
+ return yield* Either.left(new TransactingError({ message: 'No registrations found in dustActions' }));
164
+ }
165
+ return yield* LedgerOps.ledgerTry(() => {
166
+ const signature = new SignatureEnabled(signatureData);
167
+ const registrationWithSignature = new DustRegistration(signature.instance, registration.nightKey, registration.dustAddress, registration.allowFeePayment, signature);
168
+ const newDustActions = new DustActions(signature.instance, ProofMarker.preProof, dustActions.ctime, dustActions.spends, [registrationWithSignature, ...restRegistrations]);
169
+ const newIntent = Intent.deserialize(signature.instance, ProofMarker.preProof, BindingMarker.preBinding, intent.serialize());
170
+ newIntent.dustActions = newDustActions;
171
+ const newTransaction = Transaction.deserialize(signature.instance, ProofMarker.preProof, BindingMarker.preBinding, transaction.serialize());
172
+ newTransaction.intents = newTransaction.intents.set(1, newIntent);
173
+ return newTransaction;
174
+ });
175
+ });
176
+ }
111
177
  addDustGenerationSignature(transaction, signatureData) {
112
178
  return Either.gen(this, function* () {
113
179
  const intent = transaction.intents?.get(1);
@@ -167,9 +233,7 @@ export class TransactingCapabilityImplementation {
167
233
  const [first, ...rest] = transactions.map((tx) => tx.eraseProofs());
168
234
  const mergedExisting = first ? rest.reduce((acc, tx) => acc.merge(tx), first) : undefined;
169
235
  const segmentId = mergedExisting ? Option.getOrElse(findAvailableSegmentId(mergedExisting), () => 1) : 1;
170
- // @TODO in ledger 8.1.0 will be able to set the segment id when constructing the tx
171
- const balancingTx = Transaction.fromParts(network, undefined, undefined, undefined);
172
- balancingTx.intents = new Map([[segmentId, intent]]);
236
+ const balancingTx = Transaction.fromParts(network).addIntent({ tag: 'specific', value: segmentId }, intent);
173
237
  const erasedBalancing = balancingTx.eraseProofs();
174
238
  const mergedTx = mergedExisting ? mergedExisting.merge(erasedBalancing) : erasedBalancing;
175
239
  return this.calculateFee(mergedTx, ledgerParams);
@@ -197,14 +261,15 @@ export class TransactingCapabilityImplementation {
197
261
  })),
198
262
  initialImbalances: CapImbalances.fromEntry('dust', currentFee),
199
263
  feeTokenType: 'dust',
264
+ coinSelection: this.getCoinSelection(),
200
265
  transactionCostModel: {
201
266
  inputFeeOverhead: 0n,
202
267
  outputFeeOverhead: 0n,
203
268
  },
204
269
  createOutput: (coin) => coin,
205
- isCoinEqual: (a, b) => a.type === b.type && a.value === b.value,
270
+ isCoinEqual: (a, b) => a.token.nonce === b.token.nonce,
206
271
  });
207
- const recipeInputs = recipe.inputs.map((input) => ({ token: input.token, value: input.value }));
272
+ const recipeInputs = recipe.inputs.map(({ token, value }) => ({ token, value }));
208
273
  const newFee = this.dryRunFee(recipeInputs, transactions, secretKey, state, ttl, currentTime, ledgerParams);
209
274
  const recipeAmountCoverage = recipeInputs.reduce((sum, input) => sum + input.value, 0n);
210
275
  return { currentFee: newFee, recipeInputs, converged: newFee <= recipeAmountCoverage };
@@ -243,8 +308,7 @@ export class TransactingCapabilityImplementation {
243
308
  const [first, ...rest] = transactions.map((tx) => tx.eraseProofs());
244
309
  const mergedExisting = first ? rest.reduce((acc, tx) => acc.merge(tx), first) : undefined;
245
310
  const segmentId = mergedExisting ? Option.getOrElse(findAvailableSegmentId(mergedExisting), () => 1) : 1;
246
- const feeTransaction = Transaction.fromParts(networkId, undefined, undefined, undefined);
247
- feeTransaction.intents = new Map([[segmentId, intent]]);
311
+ const feeTransaction = Transaction.fromParts(networkId).addIntent({ tag: 'specific', value: segmentId }, intent);
248
312
  return [feeTransaction, updatedState];
249
313
  });
250
314
  }));
@@ -10,7 +10,6 @@ import { type KeysCapability } from './Keys.js';
10
10
  import { type CoinsAndBalancesCapability, type CoinSelection, type DefaultCoinsAndBalancesContext } from './CoinsAndBalances.js';
11
11
  import { type DefaultTransactingConfiguration, type DefaultTransactingContext, type TransactingCapability } from './Transacting.js';
12
12
  import { type NetworkId } from './types/ledger.js';
13
- import { type Dust } from './types/Dust.js';
14
13
  import { type SerializationCapability } from './Serialization.js';
15
14
  import { type TotalCostParameters } from './types/transaction.js';
16
15
  export type BaseV1Configuration = {
@@ -43,7 +42,7 @@ export declare class V1Builder<TConfig extends BaseV1Configuration = BaseV1Confi
43
42
  withSerialization<TSerializationConfig, TSerializationContext extends Partial<RunningV1Variant.AnyContext>, TSerialized>(serializationCapability: (configuration: TSerializationConfig, getContext: () => TSerializationContext) => SerializationCapability<CoreWallet, null, TSerialized>): V1Builder<TConfig & TSerializationConfig, TContext & TSerializationContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
44
43
  withTransactingDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, FinalizedTransaction, TStartAux>): V1Builder<TConfig & DefaultTransactingConfiguration, TContext & DefaultTransactingContext, TSerialized, TSyncUpdate, FinalizedTransaction, TStartAux>;
45
44
  withTransacting<TTransactingConfig, TTransactingContext extends Partial<RunningV1Variant.AnyContext>>(transactingCapability: (config: TTransactingConfig, getContext: () => TTransactingContext) => TransactingCapability<DustSecretKey, CoreWallet, TTransaction>): V1Builder<TConfig & TTransactingConfig, TContext & TTransactingContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
46
- withCoinSelection<TCoinSelectionConfig, TCoinSelectionContext extends Partial<RunningV1Variant.AnyContext>>(coinSelection: (config: TCoinSelectionConfig, getContext: () => TCoinSelectionContext) => CoinSelection<Dust>): V1Builder<TConfig & TCoinSelectionConfig, TContext & TCoinSelectionContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
45
+ withCoinSelection<TCoinSelectionConfig, TCoinSelectionContext extends Partial<RunningV1Variant.AnyContext>>(coinSelection: (config: TCoinSelectionConfig, getContext: () => TCoinSelectionContext) => CoinSelection): V1Builder<TConfig & TCoinSelectionConfig, TContext & TCoinSelectionContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
47
46
  withCoinSelectionDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
48
47
  withCoinsAndBalancesDefaults(): V1Builder<TConfig, TContext & DefaultCoinsAndBalancesContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
49
48
  withCoinsAndBalances<TBalancesConfig, TBalancesContext extends Partial<RunningV1Variant.AnyContext>>(coinsAndBalancesCapability: (configuration: TBalancesConfig, getContext: () => TBalancesContext) => CoinsAndBalancesCapability<CoreWallet>): V1Builder<TConfig & TBalancesConfig, TContext & TBalancesContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
@@ -66,7 +65,7 @@ declare namespace V1Builder {
66
65
  readonly serializationCapability: (configuration: TConfig, getContext: () => TContext) => SerializationCapability<CoreWallet, null, TSerialized>;
67
66
  };
68
67
  type HasCoinSelection<TConfig, TContext> = {
69
- readonly coinSelection: (configuration: TConfig, getContext: () => TContext) => CoinSelection<Dust>;
68
+ readonly coinSelection: (configuration: TConfig, getContext: () => TContext) => CoinSelection;
70
69
  };
71
70
  type HasCoinsAndBalances<TConfig, TContext> = {
72
71
  readonly coinsAndBalancesCapability: (configuration: TConfig, getContext: () => TContext) => CoinsAndBalancesCapability<CoreWallet>;
@@ -9,3 +9,4 @@ export * from './V1Builder.js';
9
9
  export * from './types/index.js';
10
10
  export * as CoinsAndBalances from './CoinsAndBalances.js';
11
11
  export * as TransactionHistory from './TransactionHistory.js';
12
+ export * as WalletError from './WalletError.js';
package/dist/v1/index.js CHANGED
@@ -21,3 +21,4 @@ export * from './V1Builder.js';
21
21
  export * from './types/index.js';
22
22
  export * as CoinsAndBalances from './CoinsAndBalances.js';
23
23
  export * as TransactionHistory from './TransactionHistory.js';
24
+ export * as WalletError from './WalletError.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midnight-ntwrk/wallet-sdk-dust-wallet",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -8,14 +8,16 @@
8
8
  "author": "Midnight Foundation",
9
9
  "license": "Apache-2.0",
10
10
  "publishConfig": {
11
- "registry": "https://npm.pkg.github.com/"
11
+ "registry": "https://registry.npmjs.org/",
12
+ "access": "public"
12
13
  },
13
14
  "files": [
14
15
  "dist/"
15
16
  ],
16
17
  "repository": {
17
18
  "type": "git",
18
- "url": "git+https://github.com/midnight-ntwrk/artifacts.git"
19
+ "url": "git+https://github.com/midnightntwrk/midnight-wallet.git",
20
+ "directory": "packages/dust-wallet"
19
21
  },
20
22
  "exports": {
21
23
  ".": {
@@ -28,13 +30,13 @@
28
30
  }
29
31
  },
30
32
  "dependencies": {
31
- "@midnight-ntwrk/ledger-v8": "^8.0.3",
32
- "@midnight-ntwrk/wallet-sdk-abstractions": "2.1.0",
33
- "@midnight-ntwrk/wallet-sdk-address-format": "3.1.1",
34
- "@midnight-ntwrk/wallet-sdk-capabilities": "3.3.0",
35
- "@midnight-ntwrk/wallet-sdk-indexer-client": "1.2.1",
36
- "@midnight-ntwrk/wallet-sdk-runtime": "1.0.3",
37
- "@midnight-ntwrk/wallet-sdk-utilities": "1.1.1",
33
+ "@midnight-ntwrk/ledger-v8": "^8.1.0",
34
+ "@midnight-ntwrk/wallet-sdk-abstractions": "^2.1.0",
35
+ "@midnight-ntwrk/wallet-sdk-address-format": "^3.1.2",
36
+ "@midnight-ntwrk/wallet-sdk-capabilities": "^3.3.1",
37
+ "@midnight-ntwrk/wallet-sdk-indexer-client": "^1.2.3",
38
+ "@midnight-ntwrk/wallet-sdk-runtime": "^1.0.5",
39
+ "@midnight-ntwrk/wallet-sdk-utilities": "^1.2.0",
38
40
  "effect": "^3.19.19",
39
41
  "rxjs": "^7.8.2"
40
42
  },
@@ -50,6 +52,6 @@
50
52
  "publint": "publint --strict"
51
53
  },
52
54
  "devDependencies": {
53
- "@midnight-ntwrk/wallet-sdk-hd": "3.0.2"
55
+ "@midnight-ntwrk/wallet-sdk-hd": "^3.0.3"
54
56
  }
55
57
  }