@midnight-ntwrk/wallet-sdk-dust-wallet 1.0.0-beta.9 → 1.0.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,98 @@
1
- # Wallet SDK Dust Wallet
1
+ # @midnight-ntwrk/wallet-sdk-dust-wallet
2
2
 
3
- TBD
3
+ Manages dust tokens (transaction fees) on the Midnight network.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @midnight-ntwrk/wallet-sdk-dust-wallet
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ The Dust Wallet handles dust operations on the Midnight network. Dust tokens are required to pay transaction fees. This
14
+ package provides:
15
+
16
+ - Dust coin management and tracking
17
+ - Balance synchronization with the network
18
+ - Transaction fee calculation
19
+ - Dust generation from Night UTXOs
20
+ - Fee balancing for transactions
21
+
22
+ ## Usage
23
+
24
+ ### Starting the Wallet
25
+
26
+ ```typescript
27
+ import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
28
+
29
+ await dustWallet.start(dustSecretKey);
30
+ ```
31
+
32
+ ### Observing State
33
+
34
+ ```typescript
35
+ dustWallet.state.subscribe((state) => {
36
+ console.log('Progress:', state.state.progress);
37
+ console.log('Balance:', state.state.balance);
38
+ console.log('Dust Address:', state.dustAddress);
39
+ });
40
+
41
+ // Or wait for sync
42
+ const syncedState = await dustWallet.waitForSyncedState();
43
+ ```
44
+
45
+ ### Balancing Transaction Fees
46
+
47
+ ```typescript
48
+ // Add fee balancing to a transaction
49
+ const feeBalancingTx = await dustWallet.balanceTransactions(dustSecretKey, [transactionToBalance], ttl);
50
+ ```
51
+
52
+ ### Calculating Fees
53
+
54
+ ```typescript
55
+ const fee = await dustWallet.calculateFee([transaction]);
56
+ ```
57
+
58
+ ### Dust Generation
59
+
60
+ Register Night UTXOs to generate dust:
61
+
62
+ ```typescript
63
+ const dustGenerationTx = await dustWallet.createDustGenerationTransaction(
64
+ previousState,
65
+ ttl,
66
+ nightUtxos,
67
+ nightVerifyingKey,
68
+ dustReceiverAddress,
69
+ );
70
+
71
+ // Add signature for dust registration
72
+ const signedTx = await dustWallet.addDustGenerationSignature(dustGenerationTx, signature);
73
+ ```
74
+
75
+ ## Exports
76
+
77
+ - `DustWallet` - Main wallet class
78
+ - `DustWalletState` - Wallet state type
79
+ - `DustCoreWallet` - Core wallet implementation
80
+ - `Keys` - Key management utilities
81
+ - `Simulator` - Dust simulation utilities
82
+ - `SyncService` - Synchronization service
83
+ - `Transacting` - Transaction utilities
84
+ - `CoinsAndBalances` - Coin and balance management
85
+
86
+ ## V1 Builder
87
+
88
+ Use the V1 builder pattern for wallet construction:
89
+
90
+ ```typescript
91
+ import { V1Builder, RunningV1Variant } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
92
+
93
+ // Build a V1 dust wallet variant
94
+ ```
95
+
96
+ ## License
97
+
98
+ Apache-2.0
@@ -1,10 +1,15 @@
1
1
  import { DustCoreWallet } from './DustCoreWallet.js';
2
- import { DustGenerationInfo, DustToken, DustTokenFullInfo } from './types/Dust.js';
2
+ import { KeysCapability } from './Keys.js';
3
+ import { DustGenerationDetails, DustGenerationInfo, DustToken, DustTokenFullInfo, UtxoWithMeta } from './types/Dust.js';
3
4
  export type Balance = bigint;
4
5
  export type CoinWithValue<TToken> = {
5
6
  token: TToken;
6
7
  value: Balance;
7
8
  };
9
+ export type UtxoWithFullDustDetails = Readonly<{
10
+ utxo: UtxoWithMeta;
11
+ dust: DustGenerationDetails;
12
+ }>;
8
13
  export type CoinSelection<TInput> = (coins: readonly CoinWithValue<TInput>[], amountNeeded: Balance) => CoinWithValue<TInput>[];
9
14
  export declare const chooseCoin: <TInput>(coins: readonly CoinWithValue<TInput>[], amountNeeded: Balance) => CoinWithValue<TInput>[];
10
15
  export type CoinsAndBalancesCapability<TState> = {
@@ -15,5 +20,24 @@ export type CoinsAndBalancesCapability<TState> = {
15
20
  getAvailableCoinsWithGeneratedDust(state: TState, currentTime: Date): ReadonlyArray<CoinWithValue<DustToken>>;
16
21
  getAvailableCoinsWithFullInfo(state: TState, blockTime: Date): readonly DustTokenFullInfo[];
17
22
  getGenerationInfo(state: TState, token: DustToken): DustGenerationInfo | undefined;
23
+ /**
24
+ * Splits provided Night utxos into the ones that will be used as inputs in the guaranteed and fallible sections
25
+ */
26
+ splitNightUtxos(nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>): {
27
+ guaranteed: ReadonlyArray<UtxoWithFullDustDetails>;
28
+ fallible: ReadonlyArray<UtxoWithFullDustDetails>;
29
+ };
30
+ /**
31
+ * Estimate how much Dust would be available to use if the Utxos provided were used for Dust generation from their beginning.
32
+ * This function is particularly useful for the purpose of registering for Dust generation and selecting the Utxo to be used for paying fees and approving the registration itself.
33
+ * @param state Current state of the wallet
34
+ * @param nightUtxos Existing Night utxos
35
+ * @param currentTime Current time
36
+ * @returns Estimated Dust generation per Utxo
37
+ */
38
+ estimateDustGeneration(state: TState, nightUtxos: ReadonlyArray<UtxoWithMeta>, currentTime: Date): ReadonlyArray<UtxoWithFullDustDetails>;
18
39
  };
19
- export declare const makeDefaultCoinsAndBalancesCapability: () => CoinsAndBalancesCapability<DustCoreWallet>;
40
+ export type DefaultCoinsAndBalancesContext = {
41
+ keysCapability: KeysCapability<DustCoreWallet>;
42
+ };
43
+ export declare const makeDefaultCoinsAndBalancesCapability: (config: object, getContext: () => DefaultCoinsAndBalancesContext) => CoinsAndBalancesCapability<DustCoreWallet>;
@@ -10,9 +10,9 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { Array, pipe } from 'effect';
14
- import { updatedValue } from '@midnight-ntwrk/ledger-v6';
13
+ import * as ledger from '@midnight-ntwrk/ledger-v7';
15
14
  import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
15
+ import { pipe, Array as Arr, Order } from 'effect';
16
16
  export const chooseCoin = (coins, amountNeeded) => {
17
17
  let sum = 0n;
18
18
  const sorted = coins.toSorted((a, b) => Number(a.value - b.value));
@@ -25,13 +25,14 @@ export const chooseCoin = (coins, amountNeeded) => {
25
25
  }
26
26
  return result;
27
27
  };
28
- export const makeDefaultCoinsAndBalancesCapability = () => {
28
+ const FAKE_NONCE = '0'.repeat(64);
29
+ export const makeDefaultCoinsAndBalancesCapability = (config, getContext) => {
29
30
  const getWalletBalance = (state, time) => {
30
31
  return state.state.walletBalance(time);
31
32
  };
32
33
  const getAvailableCoins = (state) => {
33
34
  const pendingSpends = new Set([...state.pendingDustTokens.values()].map((coin) => coin.nonce));
34
- return pipe(state.state.utxos, Array.filter((coin) => !pendingSpends.has(coin.nonce)));
35
+ return pipe(state.state.utxos, Arr.filter((coin) => !pendingSpends.has(coin.nonce)));
35
36
  };
36
37
  const getPendingCoins = (state) => state.pendingDustTokens;
37
38
  const getTotalCoins = (state) => [
@@ -53,7 +54,7 @@ export const makeDefaultCoinsAndBalancesCapability = () => {
53
54
  for (const coin of available) {
54
55
  const genInfo = getGenerationInfo(state, coin);
55
56
  if (genInfo) {
56
- const generatedValue = updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, state.state.params);
57
+ const generatedValue = ledger.updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, state.state.params);
57
58
  result.push({ token: coin, value: generatedValue });
58
59
  }
59
60
  }
@@ -65,19 +66,60 @@ export const makeDefaultCoinsAndBalancesCapability = () => {
65
66
  for (const coin of available) {
66
67
  const genInfo = getGenerationInfo(state, coin);
67
68
  if (genInfo) {
68
- const generatedValue = updatedValue(coin.ctime, coin.initialValue, genInfo, blockTime, state.state.params);
69
69
  result.push({
70
70
  token: coin,
71
- dtime: genInfo.dtime,
72
- maxCap: genInfo.value * state.state.params.nightDustRatio,
73
- maxCapReachedAt: DateOps.addSeconds(coin.ctime, state.state.params.timeToCapSeconds),
74
- generatedNow: generatedValue,
75
- rate: genInfo.value * state.state.params.generationDecayRate,
71
+ ...getFullDustInfo(state.state.params, genInfo, coin, blockTime),
76
72
  });
77
73
  }
78
74
  }
79
75
  return result;
80
76
  };
77
+ const getFullDustInfo = (parameters, genInfo, coin, currentTime) => {
78
+ const generatedValue = ledger.updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, parameters);
79
+ return {
80
+ dtime: genInfo.dtime,
81
+ maxCap: genInfo.value * parameters.nightDustRatio,
82
+ maxCapReachedAt: DateOps.addSeconds(coin.ctime, parameters.timeToCapSeconds),
83
+ generatedNow: generatedValue,
84
+ rate: genInfo.value * parameters.generationDecayRate,
85
+ };
86
+ };
87
+ const estimateDustGeneration = (state, nightUtxos, currentTime) => {
88
+ const dustPublicKey = getContext().keysCapability.getDustPublicKey(state);
89
+ return pipe(nightUtxos, Arr.map((utxo) => {
90
+ const genInfo = fakeGenerationInfo(utxo, dustPublicKey);
91
+ const fakeDustCoin = fakeDustToken(dustPublicKey, utxo);
92
+ const details = getFullDustInfo(state.state.params, genInfo, fakeDustCoin, currentTime);
93
+ return { utxo, dust: details };
94
+ }));
95
+ };
96
+ /**
97
+ * Create a fake generation info for a given Utxo. It allows to estimate the Dust generation from it
98
+ */
99
+ const fakeGenerationInfo = (utxo, dustPublicKey) => {
100
+ return {
101
+ value: utxo.value,
102
+ owner: dustPublicKey,
103
+ nonce: FAKE_NONCE,
104
+ dtime: undefined,
105
+ };
106
+ };
107
+ /**
108
+ * Create a fake dust coin for a given Utxo. It allows to estimate full details of the Dust generation from it
109
+ */
110
+ const fakeDustToken = (dustPublicKey, utxo) => ({
111
+ initialValue: 0n,
112
+ owner: dustPublicKey,
113
+ nonce: 0n,
114
+ seq: 0,
115
+ ctime: utxo.ctime,
116
+ backingNight: '',
117
+ mtIndex: 0n,
118
+ });
119
+ const splitNightUtxos = (utxos) => {
120
+ const [guaranteed, fallible] = pipe(utxos, Arr.sort(pipe(Order.bigint, Order.reverse, Order.mapInput((coin) => coin.dust.generatedNow))), Arr.splitAt(1));
121
+ return { guaranteed, fallible };
122
+ };
81
123
  return {
82
124
  getWalletBalance,
83
125
  getAvailableCoins,
@@ -86,5 +128,7 @@ export const makeDefaultCoinsAndBalancesCapability = () => {
86
128
  getAvailableCoinsWithGeneratedDust,
87
129
  getAvailableCoinsWithFullInfo,
88
130
  getGenerationInfo,
131
+ estimateDustGeneration,
132
+ splitNightUtxos,
89
133
  };
90
134
  };
@@ -1,4 +1,4 @@
1
- import { Bindingish, DustLocalState, DustNullifier, DustParameters, DustPublicKey, DustSecretKey, Proofish, Signaturish, Transaction, Event } from '@midnight-ntwrk/ledger-v6';
1
+ import { Bindingish, DustLocalState, DustNullifier, DustParameters, DustPublicKey, DustSecretKey, Proofish, Signaturish, Transaction, Event } from '@midnight-ntwrk/ledger-v7';
2
2
  import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
3
  import { SyncProgress } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
4
  import { DustToken, DustTokenWithNullifier } from './types/Dust.js';
@@ -10,7 +10,7 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { DustLocalState, } from '@midnight-ntwrk/ledger-v6';
13
+ import { DustLocalState, } from '@midnight-ntwrk/ledger-v7';
14
14
  import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
15
15
  import { SyncProgress } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
16
16
  import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
@@ -52,6 +52,7 @@ export class DustCoreWallet {
52
52
  applyEvents(secretKey, events, currentTime) {
53
53
  if (!events.length)
54
54
  return this;
55
+ // TODO: replace currentTime with `updatedState.syncTime` introduced in ledger-6.2.0-rc.1
55
56
  const updatedState = this.state.replayEvents(secretKey, events).processTtls(currentTime);
56
57
  let updatedPending = this.pendingDustTokens;
57
58
  if (updatedPending.length) {
@@ -1,9 +1,9 @@
1
- import { DustParameters, DustPublicKey, DustSecretKey, FinalizedTransaction, Signature, SignatureVerifyingKey, UnprovenTransaction } from '@midnight-ntwrk/ledger-v6';
1
+ import { DustParameters, DustPublicKey, DustSecretKey, FinalizedTransaction, Signature, SignatureVerifyingKey, UnprovenTransaction } from '@midnight-ntwrk/ledger-v7';
2
2
  import { ProtocolState, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
3
  import { Variant, WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
4
- import { ProvingRecipe, TransactionHistory } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { TransactionHistory } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
5
  import * as rx from 'rxjs';
6
- import { Balance, CoinsAndBalancesCapability } from './CoinsAndBalances.js';
6
+ import { Balance, CoinsAndBalancesCapability, UtxoWithFullDustDetails } from './CoinsAndBalances.js';
7
7
  import { DustCoreWallet } from './DustCoreWallet.js';
8
8
  import { KeysCapability } from './Keys.js';
9
9
  import { SerializationCapability } from './Serialization.js';
@@ -30,16 +30,17 @@ export declare class DustWalletState {
30
30
  constructor(state: ProtocolState.ProtocolState<DustCoreWallet>, capabilities: DustWalletCapabilities);
31
31
  walletBalance(time: Date): Balance;
32
32
  availableCoinsWithFullInfo(time: Date): readonly DustTokenFullInfo[];
33
+ estimateDustGeneration(nightUtxos: ReadonlyArray<UtxoWithMeta>, currentTime: Date): ReadonlyArray<UtxoWithFullDustDetails>;
33
34
  serialize(): string;
34
35
  }
35
36
  export interface DustWallet extends WalletLike.WalletLike<[Variant.VersionedVariant<DefaultV1Variant>]> {
36
37
  readonly state: rx.Observable<DustWalletState>;
37
38
  start(secretKey: DustSecretKey): Promise<void>;
38
- createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: Array<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Promise<UnprovenTransaction>;
39
- addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<ProvingRecipe.ProvingRecipe<FinalizedTransaction>>;
40
- calculateFee(transaction: AnyTransaction): Promise<bigint>;
41
- addFeePayment(secretKey: DustSecretKey, transaction: UnprovenTransaction, currentTime: Date, ttl: Date): Promise<ProvingRecipe.ProvingRecipe<FinalizedTransaction>>;
42
- finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>): Promise<FinalizedTransaction>;
39
+ createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: Array<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Promise<UnprovenTransaction>;
40
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<UnprovenTransaction>;
41
+ calculateFee(transactions: ReadonlyArray<AnyTransaction>): Promise<bigint>;
42
+ balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Promise<UnprovenTransaction>;
43
+ proveTransaction(transaction: UnprovenTransaction): Promise<FinalizedTransaction>;
43
44
  readonly submitTransaction: SubmitTransactionMethod<FinalizedTransaction>;
44
45
  serializeState(): Promise<string>;
45
46
  waitForSyncedState(allowedGap?: bigint): Promise<DustWalletState>;
@@ -10,7 +10,7 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { DustSecretKey, } from '@midnight-ntwrk/ledger-v6';
13
+ import { DustSecretKey, } from '@midnight-ntwrk/ledger-v7';
14
14
  import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
15
15
  import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
16
16
  import { WalletBuilder } from '@midnight-ntwrk/wallet-sdk-runtime';
@@ -60,6 +60,9 @@ export class DustWalletState {
60
60
  availableCoinsWithFullInfo(time) {
61
61
  return this.capabilities.coinsAndBalances.getAvailableCoinsWithFullInfo(this.state, time);
62
62
  }
63
+ estimateDustGeneration(nightUtxos, currentTime) {
64
+ return this.capabilities.coinsAndBalances.estimateDustGeneration(this.state, nightUtxos, currentTime);
65
+ }
63
66
  serialize() {
64
67
  return this.capabilities.serialization.serialize(this.state);
65
68
  }
@@ -103,24 +106,24 @@ export function DustWallet(configuration) {
103
106
  })
104
107
  .pipe(Effect.runPromise);
105
108
  }
106
- calculateFee(transaction) {
109
+ calculateFee(transactions) {
107
110
  return this.runtime
108
111
  .dispatch({
109
- [V1Tag]: (v1) => v1.calculateFee(transaction),
112
+ [V1Tag]: (v1) => v1.calculateFee(transactions),
110
113
  })
111
114
  .pipe(Effect.runPromise);
112
115
  }
113
- addFeePayment(secretKey, transaction, currentTime, ttl) {
116
+ balanceTransactions(secretKey, transactions, ttl, currentTime) {
114
117
  return this.runtime
115
118
  .dispatch({
116
- [V1Tag]: (v1) => v1.addFeePayment(secretKey, transaction, currentTime, ttl),
119
+ [V1Tag]: (v1) => v1.balanceTransactions(secretKey, transactions, ttl, currentTime),
117
120
  })
118
121
  .pipe(Effect.runPromise);
119
122
  }
120
- finalizeTransaction(recipe) {
123
+ proveTransaction(transaction) {
121
124
  return this.runtime
122
125
  .dispatch({
123
- [V1Tag]: (v1) => v1.finalizeTransaction(recipe),
126
+ [V1Tag]: (v1) => v1.proveTransaction(transaction),
124
127
  })
125
128
  .pipe(Effect.runPromise);
126
129
  }
package/dist/Keys.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DustPublicKey } from '@midnight-ntwrk/ledger-v6';
1
+ import { DustPublicKey } from '@midnight-ntwrk/ledger-v7';
2
2
  import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
3
3
  import { DustCoreWallet } from './DustCoreWallet.js';
4
4
  export type KeysCapability<TState> = {
@@ -1,6 +1,6 @@
1
1
  import { Effect, Stream, Scope } from 'effect';
2
- import { DustSecretKey, Signature, SignatureVerifyingKey, FinalizedTransaction, UnprovenTransaction } from '@midnight-ntwrk/ledger-v6';
3
- import { Proving, ProvingRecipe, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
2
+ import { DustSecretKey, Signature, SignatureVerifyingKey, FinalizedTransaction, UnprovenTransaction } from '@midnight-ntwrk/ledger-v7';
3
+ import { Proving, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
4
  import { WalletRuntimeError, Variant, StateChange } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
5
5
  import { DustToken, UtxoWithMeta } from './types/Dust.js';
6
6
  import { KeysCapability } from './Keys.js';
@@ -35,10 +35,10 @@ export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction, TS
35
35
  constructor(scope: Scope.Scope, context: Variant.VariantContext<DustCoreWallet>, v1Context: RunningV1Variant.Context<TSerialized, TSyncUpdate, TTransaction, TStartAux>);
36
36
  startSyncInBackground(startAux: TStartAux): Effect.Effect<void>;
37
37
  startSync(startAux: TStartAux): Stream.Stream<void, WalletError.WalletError, Scope.Scope>;
38
- createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Effect.Effect<UnprovenTransaction, WalletError.WalletError>;
39
- addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
40
- calculateFee(transaction: AnyTransaction): Effect.Effect<bigint, WalletError.WalletError>;
41
- addFeePayment(secretKey: DustSecretKey, transaction: UnprovenTransaction, currentTime: Date, ttl: Date): Effect.Effect<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
42
- finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Effect.Effect<TTransaction, WalletError.WalletError>;
38
+ createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Effect.Effect<UnprovenTransaction, WalletError.WalletError>;
39
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError.WalletError>;
40
+ calculateFee(transactions: ReadonlyArray<AnyTransaction>): Effect.Effect<bigint, WalletError.WalletError>;
41
+ balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<UnprovenTransaction, WalletError.WalletError>;
42
+ proveTransaction(transaction: UnprovenTransaction): Effect.Effect<TTransaction, WalletError.WalletError>;
43
43
  submitTransaction: SubmitTransactionMethod<TTransaction>;
44
44
  }
@@ -10,11 +10,11 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { Effect, SubscriptionRef, Stream, pipe, Scope, Sink, Console, Duration, Schedule } from 'effect';
14
- import { nativeToken, updatedValue, } from '@midnight-ntwrk/ledger-v6';
13
+ import { Effect, SubscriptionRef, Stream, pipe, Scope, Sink, Console, Duration, Schedule, Array as Arr } from 'effect';
14
+ import { nativeToken, } from '@midnight-ntwrk/ledger-v7';
15
15
  import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
16
16
  import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
17
- import { EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
17
+ import { ArrayOps, EitherOps } from '@midnight-ntwrk/wallet-sdk-utilities';
18
18
  import { StateChange, VersionChangeType, } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
19
19
  const progress = (state) => {
20
20
  const appliedIndex = state.progress?.appliedIndex ?? 0n;
@@ -81,19 +81,9 @@ export class RunningV1Variant {
81
81
  if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
82
82
  return Effect.fail(WalletError.WalletError.other('Token of a non-Night type received'));
83
83
  }
84
- return Effect.Do.pipe(Effect.bind('currentState', () => SubscriptionRef.get(this.#context.stateRef)), Effect.bind('utxosWithDustValue', ({ currentState }) => LedgerOps.ledgerTry(() => {
85
- const dustPublicKey = this.#v1Context.keysCapability.getDustPublicKey(currentState);
86
- return nightUtxos.map((utxo) => {
87
- const genInfo = {
88
- value: utxo.value,
89
- owner: dustPublicKey,
90
- nonce: LedgerOps.randomNonce(),
91
- dtime: undefined,
92
- };
93
- const dustValue = updatedValue(utxo.ctime, 0n, genInfo, currentTime, currentState.state.params);
94
- return { token: utxo, value: dustValue };
95
- });
96
- })), Effect.flatMap(({ utxosWithDustValue }) => {
84
+ 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 }) => {
85
+ return this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, currentTime);
86
+ }), Effect.flatMap(({ utxosWithDustValue, currentTime }) => {
97
87
  return this.#v1Context.transactingCapability
98
88
  .createDustGenerationTransaction(currentTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
99
89
  .pipe(EitherOps.toEffect);
@@ -104,22 +94,22 @@ export class RunningV1Variant {
104
94
  .addDustGenerationSignature(transaction, signature)
105
95
  .pipe(EitherOps.toEffect);
106
96
  }
107
- calculateFee(transaction) {
108
- return pipe(this.#v1Context.syncService.ledgerParameters(), Effect.map((params) => this.#v1Context.transactingCapability.calculateFee(transaction, params)));
97
+ calculateFee(transactions) {
98
+ return pipe(this.#v1Context.syncService.blockData(), Effect.map((blockData) => pipe(transactions, Arr.map((transaction) => this.#v1Context.transactingCapability.calculateFee(transaction, blockData.ledgerParameters)), ArrayOps.sumBigInt)));
109
99
  }
110
- addFeePayment(secretKey, transaction, currentTime, ttl) {
100
+ balanceTransactions(secretKey, transactions, ttl, currentTime) {
111
101
  return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
112
- return pipe(this.#v1Context.syncService.ledgerParameters(), Effect.flatMap((params) => this.#v1Context.transactingCapability.addFeePayment(secretKey, state, transaction, currentTime, ttl, params)), Effect.map(({ recipe, newState }) => [recipe, newState]));
102
+ return pipe(this.#v1Context.syncService.blockData(), Effect.flatMap((blockData) => this.#v1Context.transactingCapability.balanceTransactions(secretKey, state, transactions, ttl, currentTime ?? blockData.timestamp, blockData.ledgerParameters)));
113
103
  });
114
104
  }
115
- finalizeTransaction(recipe) {
105
+ proveTransaction(transaction) {
116
106
  return this.#v1Context.provingService
117
- .prove(recipe)
118
- .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revertRecipe(state, recipe)))));
107
+ .prove(transaction)
108
+ .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revertTransaction(state, transaction)))));
119
109
  }
120
110
  submitTransaction = ((transaction, waitForStatus = 'InBlock') => {
121
111
  return this.#v1Context.submissionService
122
112
  .submitTransaction(transaction, waitForStatus)
123
- .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revert(state, transaction)))));
113
+ .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revertTransaction(state, transaction)))));
124
114
  });
125
115
  }
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { Effect, ParseResult, Either, pipe, Schema } from 'effect';
14
- import * as ledger from '@midnight-ntwrk/ledger-v6';
14
+ import * as ledger from '@midnight-ntwrk/ledger-v7';
15
15
  import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
16
16
  import { DustCoreWallet } from './DustCoreWallet.js';
17
17
  const StateSchema = Schema.declare((input) => input instanceof ledger.DustLocalState).annotations({
@@ -1,5 +1,5 @@
1
1
  import { Effect, Either, Scope, Stream, SubscriptionRef } from 'effect';
2
- import { LedgerState, BlockContext, UserAddress, SignatureVerifyingKey, WellFormedStrictness, TransactionResult, ProofErasedTransaction, SyntheticCost } from '@midnight-ntwrk/ledger-v6';
2
+ import { LedgerState, BlockContext, UserAddress, SignatureVerifyingKey, WellFormedStrictness, TransactionResult, ProofErasedTransaction, SyntheticCost } from '@midnight-ntwrk/ledger-v7';
3
3
  import { LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
4
4
  import { NetworkId } from './types/ledger.js';
5
5
  export type SimulatorState = Readonly<{
@@ -11,6 +11,7 @@ export type SimulatorState = Readonly<{
11
11
  }>;
12
12
  export declare class Simulator {
13
13
  #private;
14
+ static blockHash: (blockTime: Date) => Effect.Effect<string>;
14
15
  static nextBlockContext: (blockTime: Date) => Effect.Effect<BlockContext>;
15
16
  static init(networkId: NetworkId): Effect.Effect<Simulator, never, Scope.Scope>;
16
17
  static apply(simulatorState: SimulatorState, tx: ProofErasedTransaction, strictness: WellFormedStrictness, blockContext: BlockContext, blockFullness?: SyntheticCost): Either.Either<[{
package/dist/Simulator.js CHANGED
@@ -11,14 +11,15 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { Effect, Encoding, pipe, Stream, SubscriptionRef } from 'effect';
14
- import { LedgerState, ClaimRewardsTransaction, SignatureErased, Transaction, WellFormedStrictness, TransactionContext, } from '@midnight-ntwrk/ledger-v6';
14
+ import { LedgerState, ClaimRewardsTransaction, SignatureErased, Transaction, WellFormedStrictness, TransactionContext, } from '@midnight-ntwrk/ledger-v7';
15
15
  import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
16
16
  import * as crypto from 'crypto';
17
17
  const simpleHash = (input) => {
18
18
  return Encoding.decodeHex(input).pipe(EitherOps.toEffect, Effect.andThen((parsed) => Effect.promise(() => crypto.subtle.digest('SHA-256', parsed))), Effect.andThen((out) => Encoding.encodeHex(new Uint8Array(out))), Effect.orDie);
19
19
  };
20
20
  export class Simulator {
21
- static nextBlockContext = (blockTime) => pipe(DateOps.dateToSeconds(blockTime).toString(16), (str) => (str.length % 2 == 0 ? str : str.padStart(str.length + 1, '0')), simpleHash, Effect.map((hash) => ({
21
+ static blockHash = (blockTime) => pipe(DateOps.dateToSeconds(blockTime).toString(16), (str) => (str.length % 2 == 0 ? str : str.padStart(str.length + 1, '0')), simpleHash);
22
+ static nextBlockContext = (blockTime) => pipe(Simulator.blockHash(blockTime), Effect.map((hash) => ({
22
23
  parentBlockHash: hash,
23
24
  secondsSinceEpoch: DateOps.dateToSeconds(blockTime),
24
25
  secondsSinceEpochErr: 1,
@@ -44,6 +45,8 @@ export class Simulator {
44
45
  static apply(simulatorState, tx, strictness, blockContext, blockFullness) {
45
46
  return LedgerOps.ledgerTry(() => {
46
47
  blockFullness = blockFullness ?? tx.cost(simulatorState.ledger.parameters);
48
+ const detailedBlockFullness = simulatorState.ledger.parameters.normalizeFullness(blockFullness);
49
+ const computedBlockFullness = Math.max(detailedBlockFullness.readTime, detailedBlockFullness.computeTime, detailedBlockFullness.blockUsage, detailedBlockFullness.bytesWritten, detailedBlockFullness.bytesChurned);
47
50
  const blockNumber = blockContext.secondsSinceEpoch;
48
51
  const blockTime = DateOps.secondsToDate(blockNumber);
49
52
  const verifiedTransaction = tx.wellFormed(simulatorState.ledger, strictness, blockTime);
@@ -51,7 +54,7 @@ export class Simulator {
51
54
  const [newLedgerState, txResult] = simulatorState.ledger.apply(verifiedTransaction, transactionContext);
52
55
  const newSimulatorState = {
53
56
  ...simulatorState,
54
- ledger: newLedgerState.postBlockUpdate(blockTime, blockFullness),
57
+ ledger: newLedgerState.postBlockUpdate(blockTime, detailedBlockFullness, computedBlockFullness),
55
58
  lastTx: tx,
56
59
  lastTxResult: txResult,
57
60
  lastTxNumber: blockNumber,
@@ -1,6 +1,6 @@
1
1
  import { Effect } from 'effect';
2
2
  import { SubmissionEvent as SubmissionEventImported } from '@midnight-ntwrk/wallet-sdk-node-client/effect';
3
- import { FinalizedTransaction, ProofErasedTransaction } from '@midnight-ntwrk/ledger-v6';
3
+ import { FinalizedTransaction, ProofErasedTransaction } from '@midnight-ntwrk/ledger-v7';
4
4
  import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
5
  import { Simulator } from './Simulator.js';
6
6
  import { NetworkId } from './types/ledger.js';
package/dist/Sync.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Effect, Layer, Schema, Scope, Stream } from 'effect';
2
- import { DustSecretKey, Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
2
+ import { DustSecretKey, Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v7';
3
3
  import { SubscriptionClient, QueryClient } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
4
4
  import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
5
  import { Simulator, SimulatorState } from './Simulator.js';
@@ -7,7 +7,13 @@ import { DustCoreWallet } from './DustCoreWallet.js';
7
7
  import { NetworkId } from './types/ledger.js';
8
8
  export interface SyncService<TState, TStartAux, TUpdate> {
9
9
  updates: (state: TState, auxData: TStartAux) => Stream.Stream<TUpdate, WalletError.WalletError, Scope.Scope>;
10
- ledgerParameters: () => Effect.Effect<LedgerParameters, WalletError.WalletError>;
10
+ blockData: () => Effect.Effect<BlockData, WalletError.WalletError>;
11
+ }
12
+ export interface BlockData {
13
+ hash: string;
14
+ height: number;
15
+ ledgerParameters: LedgerParameters;
16
+ timestamp: Date;
11
17
  }
12
18
  export interface SyncCapability<TState, TUpdate> {
13
19
  applyUpdate: (state: TState, update: TUpdate) => TState;
@@ -39,11 +45,12 @@ export declare const SyncEventsUpdateSchema: Schema.Struct<{
39
45
  }>;
40
46
  export type WalletSyncSubscription = Schema.Schema.Type<typeof SyncEventsUpdateSchema>;
41
47
  export type WalletSyncUpdate = {
42
- update: WalletSyncSubscription;
48
+ updates: WalletSyncSubscription[];
43
49
  secretKeys: SecretKeysResource;
50
+ timestamp: Date;
44
51
  };
45
52
  export declare const WalletSyncUpdate: {
46
- create: (update: WalletSyncSubscription, secretKey: DustSecretKey) => WalletSyncUpdate;
53
+ create: (updates: WalletSyncSubscription[], secretKey: DustSecretKey, timestamp: Date) => WalletSyncUpdate;
47
54
  };
48
55
  export declare const makeDefaultSyncService: (config: DefaultSyncConfiguration) => SyncService<DustCoreWallet, DustSecretKey, WalletSyncUpdate>;
49
56
  export type IndexerSyncService = {
package/dist/Sync.js CHANGED
@@ -10,13 +10,14 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { Effect, Either, Layer, ParseResult, pipe, Schema, Stream } from 'effect';
14
- import { Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
13
+ import { Effect, Either, Layer, ParseResult, pipe, Schema, Stream, Duration, Chunk, Schedule } from 'effect';
14
+ import { Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v7';
15
15
  import { BlockHash, DustLedgerEvents } from '@midnight-ntwrk/wallet-sdk-indexer-client';
16
16
  import { WsSubscriptionClient, HttpQueryClient, ConnectionHelper, } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
17
17
  import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
18
18
  import { WsURL } from '@midnight-ntwrk/wallet-sdk-utilities/networking';
19
19
  import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
20
+ import { Simulator } from './Simulator.js';
20
21
  import { Uint8ArraySchema } from './Serialization.js';
21
22
  export const SecretKeysResource = {
22
23
  create: (secretKey) => {
@@ -57,10 +58,11 @@ export const SyncEventsUpdateSchema = Schema.Struct({
57
58
  maxId: Schema.Number,
58
59
  });
59
60
  export const WalletSyncUpdate = {
60
- create: (update, secretKey) => {
61
+ create: (updates, secretKey, timestamp) => {
61
62
  return {
62
- update,
63
+ updates,
63
64
  secretKeys: SecretKeysResource.create(secretKey),
65
+ timestamp,
64
66
  };
65
67
  },
66
68
  };
@@ -68,18 +70,26 @@ export const makeDefaultSyncService = (config) => {
68
70
  const indexerSyncService = makeIndexerSyncService(config);
69
71
  return {
70
72
  updates: (state, secretKey) => {
71
- return pipe(indexerSyncService.subscribeWallet(state), Stream.map((data) => WalletSyncUpdate.create(data, secretKey)), Stream.provideSomeLayer(indexerSyncService.connectionLayer()));
73
+ const batchSize = 10;
74
+ const batchTimeout = Duration.millis(1);
75
+ return pipe(indexerSyncService.subscribeWallet(state), Stream.groupedWithin(batchSize, batchTimeout), Stream.map(Chunk.toArray), Stream.map((data) => WalletSyncUpdate.create(data, secretKey, new Date())), Stream.schedule(Schedule.spaced(Duration.millis(4))), Stream.provideSomeLayer(indexerSyncService.connectionLayer()));
72
76
  },
73
- ledgerParameters: () => {
77
+ blockData: () => {
74
78
  return Effect.gen(function* () {
75
79
  const query = yield* BlockHash;
76
80
  const result = yield* query({ offset: null });
77
- return result.block?.ledgerParameters;
78
- }).pipe(Effect.provide(indexerSyncService.queryClient()), Effect.scoped, Effect.catchAll((err) => Effect.fail(WalletError.WalletError.other(`Encountered unexpected error: ${err.message}`))), Effect.flatMap((ledgerParameters) => {
79
- if (ledgerParameters === undefined) {
80
- return Effect.fail(WalletError.WalletError.other('Unable to fetch ledger parameters'));
81
+ return result.block;
82
+ }).pipe(Effect.provide(indexerSyncService.queryClient()), Effect.scoped, Effect.catchAll((err) => Effect.fail(WalletError.WalletError.other(`Encountered unexpected error: ${err.message}`))), Effect.flatMap((blockData) => {
83
+ if (!blockData) {
84
+ return Effect.fail(WalletError.WalletError.other('Unable to fetch block data'));
81
85
  }
82
- return LedgerOps.ledgerTry(() => LedgerParameters.deserialize(Buffer.from(ledgerParameters, 'hex')));
86
+ // TODO: convert to schema
87
+ return LedgerOps.ledgerTry(() => ({
88
+ hash: blockData.hash,
89
+ height: blockData.height,
90
+ ledgerParameters: LedgerParameters.deserialize(Buffer.from(blockData.ledgerParameters, 'hex')),
91
+ timestamp: new Date(blockData.timestamp),
92
+ }));
83
93
  }));
84
94
  },
85
95
  };
@@ -107,17 +117,22 @@ export const makeIndexerSyncService = (config) => {
107
117
  export const makeDefaultSyncCapability = () => {
108
118
  return {
109
119
  applyUpdate(state, wrappedUpdate) {
110
- const { update, secretKeys } = wrappedUpdate;
111
- const nextIndex = BigInt(update.id);
112
- const highestRelevantWalletIndex = BigInt(update.maxId);
120
+ const { updates, secretKeys } = wrappedUpdate;
121
+ // Nothing to update yet
122
+ if (updates.length === 0) {
123
+ return state;
124
+ }
125
+ const lastUpdate = updates.at(-1);
126
+ const nextIndex = BigInt(lastUpdate.id);
127
+ const highestRelevantWalletIndex = BigInt(lastUpdate.maxId);
113
128
  // in case the nextIndex is less than or equal to the current appliedIndex
114
129
  // just update highestRelevantWalletIndex
115
130
  if (nextIndex <= state.progress.appliedIndex) {
116
131
  return state.updateProgress({ highestRelevantWalletIndex, isConnected: true });
117
132
  }
118
- const events = [update.raw].filter((event) => event !== null);
133
+ const events = updates.map((u) => u.raw).filter((event) => event !== null);
119
134
  return secretKeys((keys) => state
120
- .applyEvents(keys, events, new Date())
135
+ .applyEvents(keys, events, wrappedUpdate.timestamp)
121
136
  .updateProgress({ appliedIndex: nextIndex, highestRelevantWalletIndex, isConnected: true }));
122
137
  },
123
138
  };
@@ -125,7 +140,18 @@ export const makeDefaultSyncCapability = () => {
125
140
  export const makeSimulatorSyncService = (config) => {
126
141
  return {
127
142
  updates: (_state, secretKey) => config.simulator.state$.pipe(Stream.map((state) => ({ update: state, secretKey }))),
128
- ledgerParameters: () => pipe(config.simulator.getLatestState(), Effect.map((state) => state.ledger.parameters)),
143
+ blockData: () => {
144
+ return Effect.gen(function* () {
145
+ const state = yield* config.simulator.getLatestState();
146
+ const timestamp = DateOps.secondsToDate(state.lastTxNumber);
147
+ return {
148
+ hash: yield* Simulator.blockHash(timestamp),
149
+ height: Number(state.lastTxNumber),
150
+ ledgerParameters: state.ledger.parameters,
151
+ timestamp,
152
+ };
153
+ });
154
+ },
129
155
  };
130
156
  };
131
157
  export const makeSimulatorSyncCapability = () => ({
@@ -1,22 +1,18 @@
1
1
  import { Either } from 'effect';
2
- import { DustSecretKey, Signature, SignatureVerifyingKey, Utxo, FinalizedTransaction, ProofErasedTransaction, UnprovenTransaction, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
3
- import { ProvingRecipe, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
2
+ import { DustSecretKey, Signature, SignatureVerifyingKey, FinalizedTransaction, ProofErasedTransaction, UnprovenTransaction, LedgerParameters } from '@midnight-ntwrk/ledger-v7';
3
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
4
  import { DustCoreWallet } from './DustCoreWallet.js';
5
5
  import { AnyTransaction, DustToken, NetworkId, TotalCostParameters } from './types/index.js';
6
- import { CoinsAndBalancesCapability, CoinSelection, CoinWithValue } from './CoinsAndBalances.js';
6
+ import { CoinsAndBalancesCapability, CoinSelection, UtxoWithFullDustDetails } from './CoinsAndBalances.js';
7
7
  import { KeysCapability } from './Keys.js';
8
8
  export interface TransactingCapability<TSecrets, TState, TTransaction> {
9
9
  readonly networkId: NetworkId;
10
10
  readonly costParams: TotalCostParameters;
11
- createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<CoinWithValue<Utxo>>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
12
- addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Either.Either<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
11
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
12
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Either.Either<UnprovenTransaction, WalletError.WalletError>;
13
13
  calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
14
- addFeePayment(secretKey: TSecrets, state: TState, transaction: UnprovenTransaction, currentTime: Date, ttl: Date, ledgerParams: LedgerParameters): Either.Either<{
15
- recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>;
16
- newState: TState;
17
- }, WalletError.WalletError>;
18
- revert(state: TState, tx: TTransaction): Either.Either<TState, WalletError.WalletError>;
19
- revertRecipe(state: TState, recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Either.Either<TState, WalletError.WalletError>;
14
+ balanceTransactions(secretKey: TSecrets, state: TState, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime: Date, ledgerParams: LedgerParameters): Either.Either<[UnprovenTransaction, TState], WalletError.WalletError>;
15
+ revertTransaction(state: TState, transaction: UnprovenTransaction | TTransaction): Either.Either<TState, WalletError.WalletError>;
20
16
  }
21
17
  export type DefaultTransactingConfiguration = {
22
18
  networkId: NetworkId;
@@ -37,14 +33,10 @@ export declare class TransactingCapabilityImplementation<TTransaction extends An
37
33
  readonly getCoins: () => CoinsAndBalancesCapability<DustCoreWallet>;
38
34
  readonly getKeys: () => KeysCapability<DustCoreWallet>;
39
35
  constructor(networkId: NetworkId, costParams: TotalCostParameters, getCoinSelection: () => CoinSelection<DustToken>, getCoins: () => CoinsAndBalancesCapability<DustCoreWallet>, getKeys: () => KeysCapability<DustCoreWallet>);
40
- createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<CoinWithValue<Utxo>>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
41
- addDustGenerationSignature(transaction: UnprovenTransaction, signatureData: Signature): Either.Either<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
36
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
37
+ addDustGenerationSignature(transaction: UnprovenTransaction, signatureData: Signature): Either.Either<UnprovenTransaction, WalletError.WalletError>;
42
38
  calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
43
39
  static feeImbalance(transaction: AnyTransaction, totalFee: bigint): bigint;
44
- addFeePayment(secretKey: DustSecretKey, state: DustCoreWallet, transaction: UnprovenTransaction, currentTime: Date, ttl: Date, ledgerParams: LedgerParameters): Either.Either<{
45
- recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>;
46
- newState: DustCoreWallet;
47
- }, WalletError.WalletError>;
48
- revert(state: DustCoreWallet, tx: TTransaction): Either.Either<DustCoreWallet, WalletError.WalletError>;
49
- revertRecipe(state: DustCoreWallet, recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Either.Either<DustCoreWallet, WalletError.WalletError>;
40
+ balanceTransactions(secretKey: DustSecretKey, state: DustCoreWallet, transactions: ReadonlyArray<FinalizedTransaction | UnprovenTransaction>, ttl: Date, currentTime: Date, ledgerParams: LedgerParameters): Either.Either<[UnprovenTransaction, DustCoreWallet], WalletError.WalletError>;
41
+ revertTransaction(state: DustCoreWallet, transaction: UnprovenTransaction | TTransaction): Either.Either<DustCoreWallet, WalletError.WalletError>;
50
42
  }
@@ -10,10 +10,10 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { Either } from 'effect';
14
- import { DustActions, DustRegistration, Intent, SignatureEnabled, Transaction, UnshieldedOffer, addressFromKey, } from '@midnight-ntwrk/ledger-v6';
13
+ import { Either, pipe, BigInt as BigIntOps, Iterable as IterableOps, Option } from 'effect';
14
+ import { DustActions, DustRegistration, Intent, SignatureEnabled, Transaction, UnshieldedOffer, addressFromKey, nativeToken, } from '@midnight-ntwrk/ledger-v7';
15
15
  import { MidnightBech32m, DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
16
- import { ProvingRecipe, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
16
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
17
17
  import { LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
18
18
  import { BindingMarker, ProofMarker, SignatureMarker } from './Utils.js';
19
19
  export const makeDefaultTransactingCapability = (config, getContext) => {
@@ -36,24 +36,48 @@ export class TransactingCapabilityImplementation {
36
36
  this.getKeys = getKeys;
37
37
  }
38
38
  createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress) {
39
+ const makeOffer = (utxos) => {
40
+ if (utxos.length === 0) {
41
+ return Option.none();
42
+ }
43
+ const totalValue = pipe(utxos, IterableOps.map((coin) => coin.utxo.value), BigIntOps.sumAll);
44
+ const inputs = utxos.map(({ utxo }) => ({
45
+ ...utxo,
46
+ owner: nightVerifyingKey,
47
+ }));
48
+ const output = {
49
+ owner: addressFromKey(nightVerifyingKey),
50
+ type: nativeToken().raw,
51
+ value: totalValue,
52
+ };
53
+ return Option.some(UnshieldedOffer.new(inputs, [output], []));
54
+ };
39
55
  return Either.gen(this, function* () {
40
56
  const receiver = dustReceiverAddress ? yield* this.#parseAddress(dustReceiverAddress) : undefined;
41
57
  return yield* LedgerOps.ledgerTry(() => {
42
58
  const network = this.networkId;
43
- const intent = Intent.new(ttl);
44
- const totalDustValue = nightUtxos.reduce((total, { value }) => total + value, 0n);
45
- const inputs = nightUtxos.map(({ token: utxo }) => ({
46
- ...utxo,
47
- owner: nightVerifyingKey,
48
- }));
49
- const outputs = inputs.map((input) => ({
50
- owner: addressFromKey(nightVerifyingKey),
51
- type: input.type,
52
- value: input.value,
53
- }));
54
- intent.guaranteedUnshieldedOffer = UnshieldedOffer.new(inputs, outputs, []);
59
+ const splitResult = this.getCoins().splitNightUtxos(nightUtxos);
60
+ const totalDustValue = pipe(splitResult.guaranteed, IterableOps.map((coin) => coin.dust.generatedNow), BigIntOps.sumAll);
61
+ const maybeGuaranteedOffer = makeOffer(splitResult.guaranteed);
62
+ const maybeFallibleOffer = makeOffer(splitResult.fallible);
55
63
  const dustRegistration = new DustRegistration(SignatureMarker.signature, nightVerifyingKey, receiver, dustReceiverAddress !== undefined ? totalDustValue : 0n);
56
- intent.dustActions = new DustActions(SignatureMarker.signature, ProofMarker.preProof, currentTime, [], [dustRegistration]);
64
+ const dustActions = new DustActions(SignatureMarker.signature, ProofMarker.preProof, currentTime, [], [dustRegistration]);
65
+ const intent = pipe(Intent.new(ttl), (intent) => Option.match(maybeGuaranteedOffer, {
66
+ onNone: () => intent,
67
+ onSome: (guaranteedOffer) => {
68
+ intent.guaranteedUnshieldedOffer = guaranteedOffer;
69
+ return intent;
70
+ },
71
+ }), (intent) => Option.match(maybeFallibleOffer, {
72
+ onNone: () => intent,
73
+ onSome: (fallibleOffer) => {
74
+ intent.fallibleUnshieldedOffer = fallibleOffer;
75
+ return intent;
76
+ },
77
+ }), (intent) => {
78
+ intent.dustActions = dustActions;
79
+ return intent;
80
+ });
57
81
  return Transaction.fromParts(network, undefined, undefined, intent);
58
82
  });
59
83
  });
@@ -64,7 +88,7 @@ export class TransactingCapabilityImplementation {
64
88
  if (!intent) {
65
89
  return yield* Either.left(new WalletError.TransactingError({ message: 'No intent found in the transaction intents with segment = 1' }));
66
90
  }
67
- const { dustActions, guaranteedUnshieldedOffer } = intent;
91
+ const { dustActions, guaranteedUnshieldedOffer, fallibleUnshieldedOffer } = intent;
68
92
  if (!dustActions) {
69
93
  return yield* Either.left(new WalletError.TransactingError({ message: 'No dustActions found in intent' }));
70
94
  }
@@ -88,13 +112,18 @@ export class TransactingCapabilityImplementation {
88
112
  signatures.push(guaranteedUnshieldedOffer.signatures.at(i) ?? signatureData);
89
113
  }
90
114
  newIntent.guaranteedUnshieldedOffer = guaranteedUnshieldedOffer.addSignatures(signatures);
115
+ if (fallibleUnshieldedOffer) {
116
+ const inputsLen = fallibleUnshieldedOffer.inputs.length;
117
+ const signatures = [];
118
+ for (let i = 0; i < inputsLen; ++i) {
119
+ signatures.push(fallibleUnshieldedOffer.signatures.at(i) ?? signatureData);
120
+ }
121
+ newIntent.fallibleUnshieldedOffer = fallibleUnshieldedOffer.addSignatures(signatures);
122
+ }
91
123
  // make a copy of transaction to avoid mutation
92
124
  const newTransaction = Transaction.deserialize(signature.instance, ProofMarker.preProof, BindingMarker.preBinding, transaction.serialize());
93
125
  newTransaction.intents = newTransaction.intents.set(1, newIntent);
94
- return {
95
- type: ProvingRecipe.TRANSACTION_TO_PROVE,
96
- transaction: newTransaction,
97
- };
126
+ return newTransaction;
98
127
  });
99
128
  });
100
129
  }
@@ -108,9 +137,10 @@ export class TransactingCapabilityImplementation {
108
137
  .find(([tt, _]) => tt.tag === 'dust');
109
138
  return dustImbalance ? -dustImbalance[1] : totalFee;
110
139
  }
111
- addFeePayment(secretKey, state, transaction, currentTime, ttl, ledgerParams) {
140
+ balanceTransactions(secretKey, state, transactions, ttl, currentTime, ledgerParams) {
112
141
  const network = this.networkId;
113
- const feeLeft = TransactingCapabilityImplementation.feeImbalance(transaction, this.calculateFee(transaction, ledgerParams));
142
+ const feeLeft = transactions.reduce((total, transaction) => total +
143
+ TransactingCapabilityImplementation.feeImbalance(transaction, this.calculateFee(transaction, ledgerParams)), 0n);
114
144
  const dustTokens = this.getCoins().getAvailableCoinsWithGeneratedDust(state, currentTime);
115
145
  const selectedTokens = this.getCoinSelection()(dustTokens, feeLeft);
116
146
  if (!selectedTokens.length) {
@@ -119,8 +149,7 @@ export class TransactingCapabilityImplementation {
119
149
  const totalFeeInSelected = selectedTokens.reduce((total, { value }) => total + value, 0n);
120
150
  const feeDiff = totalFeeInSelected - feeLeft;
121
151
  if (feeDiff < 0n) {
122
- // A sanity-check, should never happen
123
- return Either.left(new WalletError.TransactingError({ message: 'Error in tokens selection algorithm' }));
152
+ return Either.left(new WalletError.TransactingError({ message: 'Not enough Dust generated to pay the fee' }));
124
153
  }
125
154
  // reduce the largest token's value by `feeDiff`
126
155
  const tokensWithFeeToTake = selectedTokens.toSorted((a, b) => Number(b.value - a.value));
@@ -134,49 +163,22 @@ export class TransactingCapabilityImplementation {
134
163
  return LedgerOps.ledgerTry(() => {
135
164
  const intent = Intent.new(ttl);
136
165
  const [spends, updatedState] = state.spendCoins(secretKey, tokensWithFeeToTake, currentTime);
137
- intent.dustActions = new DustActions(SignatureMarker.signature, ProofMarker.preProof, currentTime, spends, []);
166
+ intent.dustActions = new DustActions(SignatureMarker.signature, ProofMarker.preProof, currentTime, [...spends], []);
138
167
  const feeTransaction = Transaction.fromPartsRandomized(network, undefined, undefined, intent);
139
- return {
140
- newState: updatedState,
141
- recipe: {
142
- type: ProvingRecipe.TRANSACTION_TO_PROVE,
143
- transaction: transaction.merge(feeTransaction),
144
- },
145
- };
168
+ return [feeTransaction, updatedState];
146
169
  });
147
170
  }
148
- revert(state, tx) {
171
+ revertTransaction(state, transaction) {
149
172
  return Either.try({
150
- try: () => state.revertTransaction(tx),
173
+ try: () => state.revertTransaction(transaction),
151
174
  catch: (err) => {
152
175
  return new WalletError.OtherWalletError({
153
- message: `Error while reverting transaction ${tx.identifiers().at(0)}`,
176
+ message: `Error while reverting transaction ${transaction.identifiers().at(0)}`,
154
177
  cause: err,
155
178
  });
156
179
  },
157
180
  });
158
181
  }
159
- revertRecipe(state, recipe) {
160
- const doRevert = (tx) => {
161
- return Either.try({
162
- try: () => state.revertTransaction(tx),
163
- catch: (err) => {
164
- return new WalletError.OtherWalletError({
165
- message: `Error while reverting transaction ${tx.identifiers().at(0)}`,
166
- cause: err,
167
- });
168
- },
169
- });
170
- };
171
- switch (recipe.type) {
172
- case ProvingRecipe.TRANSACTION_TO_PROVE:
173
- return doRevert(recipe.transaction);
174
- case ProvingRecipe.BALANCE_TRANSACTION_TO_PROVE:
175
- return doRevert(recipe.transactionToProve);
176
- case ProvingRecipe.NOTHING_TO_PROVE:
177
- return Either.right(state);
178
- }
179
- }
180
182
  #parseAddress(addr) {
181
183
  return Either.try({
182
184
  try: () => {
@@ -1,12 +1,12 @@
1
1
  import { Types, Either } from 'effect';
2
- import { DustSecretKey, FinalizedTransaction } from '@midnight-ntwrk/ledger-v6';
2
+ import { DustSecretKey, FinalizedTransaction } from '@midnight-ntwrk/ledger-v7';
3
3
  import { VariantBuilder, Variant } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
4
4
  import { Proving, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
5
  import { SyncService, SyncCapability, DefaultSyncConfiguration, WalletSyncUpdate } from './Sync.js';
6
6
  import { RunningV1Variant, V1Tag } from './RunningV1Variant.js';
7
7
  import { DustCoreWallet } from './DustCoreWallet.js';
8
8
  import { KeysCapability } from './Keys.js';
9
- import { CoinsAndBalancesCapability, CoinSelection } from './CoinsAndBalances.js';
9
+ import { CoinsAndBalancesCapability, CoinSelection, DefaultCoinsAndBalancesContext } from './CoinsAndBalances.js';
10
10
  import { DefaultTransactingConfiguration, DefaultTransactingContext, TransactingCapability } from './Transacting.js';
11
11
  import { NetworkId } from './types/ledger.js';
12
12
  import { DefaultSubmissionConfiguration, SubmissionService } from './Submission.js';
@@ -46,7 +46,7 @@ export declare class V1Builder<TConfig extends BaseV1Configuration = BaseV1Confi
46
46
  withCoinSelectionDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
47
47
  withProving<TProvingConfig, TProvingContext extends Partial<RunningV1Variant.AnyContext>>(provingService: (config: TProvingConfig, getContext: () => TProvingContext) => Proving.ProvingService<TTransaction>): V1Builder<TConfig & TProvingConfig, TContext & TProvingContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
48
48
  withProvingDefaults(this: V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, FinalizedTransaction, TStartAux>): V1Builder<TConfig & Proving.DefaultProvingConfiguration, TContext, TSerialized, TSyncUpdate, FinalizedTransaction, TStartAux>;
49
- withCoinsAndBalancesDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
49
+ withCoinsAndBalancesDefaults(): V1Builder<TConfig, TContext & DefaultCoinsAndBalancesContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
50
50
  withCoinsAndBalances<TBalancesConfig, TBalancesContext extends Partial<RunningV1Variant.AnyContext>>(coinsAndBalancesCapability: (configuration: TBalancesConfig, getContext: () => TBalancesContext) => CoinsAndBalancesCapability<DustCoreWallet>): V1Builder<TConfig & TBalancesConfig, TContext & TBalancesContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
51
51
  withKeysDefaults(): V1Builder<TConfig, TContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
52
52
  withKeys<TKeysConfig, TKeysContext extends Partial<RunningV1Variant.AnyContext>>(keysCapability: (configuration: TKeysConfig, getContext: () => TKeysContext) => KeysCapability<DustCoreWallet>): V1Builder<TConfig & TKeysConfig, TContext & TKeysContext, TSerialized, TSyncUpdate, TTransaction, TStartAux>;
package/dist/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export * as Transacting from './Transacting.js';
7
7
  export * from './RunningV1Variant.js';
8
8
  export * from './V1Builder.js';
9
9
  export * from './types/index.js';
10
+ export * as CoinsAndBalances from './CoinsAndBalances.js';
package/dist/index.js CHANGED
@@ -19,3 +19,4 @@ export * as Transacting from './Transacting.js';
19
19
  export * from './RunningV1Variant.js';
20
20
  export * from './V1Builder.js';
21
21
  export * from './types/index.js';
22
+ export * as CoinsAndBalances from './CoinsAndBalances.js';
@@ -1,4 +1,4 @@
1
- import { DustInitialNonce, DustNullifier, DustNonce, DustPublicKey, Utxo } from '@midnight-ntwrk/ledger-v6';
1
+ import { DustInitialNonce, DustNullifier, DustNonce, DustPublicKey, Utxo } from '@midnight-ntwrk/ledger-v7';
2
2
  export type DustToken = {
3
3
  initialValue: bigint;
4
4
  owner: DustPublicKey;
@@ -11,8 +11,10 @@ export type DustToken = {
11
11
  export type DustTokenWithNullifier = DustToken & {
12
12
  nullifier: DustNullifier;
13
13
  };
14
- export type DustTokenFullInfo = {
14
+ export type DustTokenFullInfo = DustGenerationDetails & {
15
15
  token: DustToken;
16
+ };
17
+ export type DustGenerationDetails = {
16
18
  dtime: Date | undefined;
17
19
  maxCap: bigint;
18
20
  maxCapReachedAt: Date;
@@ -1,4 +1,4 @@
1
- import * as ledger from '@midnight-ntwrk/ledger-v6';
2
- export type AnyTransaction = ledger.UnprovenTransaction | ledger.FinalizedTransaction | ledger.ProofErasedTransaction;
1
+ import * as ledger from '@midnight-ntwrk/ledger-v7';
2
+ export type AnyTransaction = ledger.UnprovenTransaction | ledger.FinalizedTransaction | ledger.ProofErasedTransaction | ledger.Transaction<ledger.SignatureEnabled, ledger.Proof, ledger.PreBinding>;
3
3
  export type UnprovenDustSpend = ledger.DustSpend<ledger.PreProof>;
4
4
  export type NetworkId = string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midnight-ntwrk/wallet-sdk-dust-wallet",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -23,25 +23,25 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@midnight-ntwrk/ledger-v6": "6.1.0-alpha.5",
27
- "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0-beta.9",
28
- "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.8",
29
- "@midnight-ntwrk/wallet-sdk-capabilities": "3.0.0-beta.8",
30
- "@midnight-ntwrk/wallet-sdk-hd": "3.0.0-beta.7",
31
- "@midnight-ntwrk/wallet-sdk-indexer-client": "1.0.0-beta.12",
32
- "@midnight-ntwrk/wallet-sdk-node-client": "1.0.0-beta.9",
33
- "@midnight-ntwrk/wallet-sdk-prover-client": "1.0.0-beta.9",
34
- "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0-beta.10",
35
- "@midnight-ntwrk/wallet-sdk-utilities": "1.0.0-beta.7",
36
- "effect": "^3.17.3",
26
+ "@midnight-ntwrk/ledger-v7": "7.0.0",
27
+ "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0",
28
+ "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0",
29
+ "@midnight-ntwrk/wallet-sdk-capabilities": "3.0.0",
30
+ "@midnight-ntwrk/wallet-sdk-hd": "3.0.0",
31
+ "@midnight-ntwrk/wallet-sdk-indexer-client": "1.0.0",
32
+ "@midnight-ntwrk/wallet-sdk-node-client": "1.0.0",
33
+ "@midnight-ntwrk/wallet-sdk-prover-client": "1.0.0",
34
+ "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0",
35
+ "@midnight-ntwrk/wallet-sdk-utilities": "1.0.0",
36
+ "effect": "^3.19.14",
37
37
  "rxjs": "^7.5"
38
38
  },
39
39
  "scripts": {
40
40
  "typecheck": "tsc -b ./tsconfig.json --noEmit",
41
41
  "test": "vitest run",
42
42
  "lint": "eslint --max-warnings 0",
43
- "format": "prettier --write \"**/*.{ts,js,json,yaml,yml}\"",
44
- "format:check": "prettier --check \"**/*.{ts,js,json,yaml,yml}\"",
43
+ "format": "prettier --write \"**/*.{ts,js,json,yaml,yml,md}\"",
44
+ "format:check": "prettier --check \"**/*.{ts,js,json,yaml,yml,md}\"",
45
45
  "dist": "tsc -b ./tsconfig.build.json",
46
46
  "dist:publish": "tsc -b ./tsconfig.publish.json",
47
47
  "clean": "rimraf --glob dist 'tsconfig.*.tsbuildinfo' && date +%s > .clean-timestamp",
@@ -50,10 +50,11 @@
50
50
  "devDependencies": {
51
51
  "@types/node": "22.17.0",
52
52
  "eslint": "^9.37.0",
53
+ "prettier": "^3.7.0",
53
54
  "publint": "~0.3.14",
54
55
  "rimraf": "^6.0.1",
55
- "testcontainers": "^11.8.1",
56
+ "testcontainers": "^11.10.0",
56
57
  "typescript": "^5.9.3",
57
- "vitest": "^3.2.4"
58
+ "vitest": "^4.0.16"
58
59
  }
59
60
  }