@midnightntwrk/wallet-sdk-dust-wallet 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @midnightntwrk/wallet-sdk-dust-wallet
2
+
3
+ Manages dust (transaction fees) on the Midnight network.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @midnightntwrk/wallet-sdk-dust-wallet
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ The Dust Wallet handles dust operations on the Midnight network. Dust is required to pay transaction fees. This package
14
+ 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 '@midnightntwrk/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
+ // Calculate the fee for a transaction only (does not include the balancing transaction fee)
56
+ const fee = await dustWallet.calculateFee([transaction]);
57
+
58
+ // Estimate the total fee including the balancing transaction fee
59
+ // ttl and currentTime are optional (default: 1 hour from now, and current block timestamp)
60
+ const totalFee = await dustWallet.estimateFee(dustSecretKey, [transaction]);
61
+
62
+ // With explicit ttl and currentTime
63
+ const totalFeeWithOptions = await dustWallet.estimateFee(dustSecretKey, [transaction], ttl, currentTime);
64
+ ```
65
+
66
+ ### Dust Generation
67
+
68
+ Register Night UTXOs to generate dust:
69
+
70
+ ```typescript
71
+ const dustGenerationTx = await dustWallet.createDustGenerationTransaction(
72
+ previousState,
73
+ ttl,
74
+ nightUtxos,
75
+ nightVerifyingKey,
76
+ dustReceiverAddress,
77
+ );
78
+
79
+ // Add signature for dust registration
80
+ const signedTx = await dustWallet.addDustGenerationSignature(dustGenerationTx, signature);
81
+ ```
82
+
83
+ ## Exports
84
+
85
+ - `DustWallet` - Main wallet class
86
+ - `DustWalletState` - Wallet state type
87
+ - `DustCoreWallet` - Core wallet implementation
88
+ - `Keys` - Key management utilities
89
+ - `Simulator` - Dust simulation utilities
90
+ - `SyncService` - Synchronization service
91
+ - `Transacting` - Transaction utilities
92
+ - `CoinsAndBalances` - Coin and balance management
93
+
94
+ ## V1 Builder
95
+
96
+ Use the V1 builder pattern for wallet construction:
97
+
98
+ ```typescript
99
+ import { V1Builder, RunningV1Variant } from '@midnightntwrk/wallet-sdk-dust-wallet';
100
+
101
+ // Build a V1 dust wallet variant
102
+ ```
103
+
104
+ ## License
105
+
106
+ Apache-2.0
@@ -0,0 +1,101 @@
1
+ import { type DustParameters, type DustPublicKey, DustSecretKey, type FinalizedTransaction, type Signature, type SignatureVerifyingKey, type UnprovenTransaction } from '@midnight-ntwrk/ledger-v8';
2
+ import { type ProtocolState, ProtocolVersion, type SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
3
+ import { type DustAddress } from '@midnightntwrk/wallet-sdk-address-format';
4
+ import { type Variant, type VariantBuilder, type WalletLike } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
5
+ import { type Clock } from '@midnightntwrk/wallet-sdk-utilities';
6
+ import * as rx from 'rxjs';
7
+ import { type Balance, type CoinsAndBalancesCapability, type UtxoWithFullDustDetails } from './v1/CoinsAndBalances.js';
8
+ import { CoreWallet } from './v1/CoreWallet.js';
9
+ import { type KeysCapability } from './v1/Keys.js';
10
+ import { type SerializationCapability } from './v1/Serialization.js';
11
+ import { type NightUtxoSplitForDustRegistration } from './v1/Transacting.js';
12
+ import { type DustFullInfo, type UtxoWithMeta } from './v1/types/Dust.js';
13
+ import { type AnyTransaction } from './v1/types/ledger.js';
14
+ import { type BaseV1Configuration, type DefaultV1Configuration, type V1Variant } from './v1/V1Builder.js';
15
+ import { type WalletSyncUpdate } from './v1/Sync.js';
16
+ import { type TransactionHistoryService } from './v1/TransactionHistory.js';
17
+ export type DustWalletCapabilities<TSerialized = string> = {
18
+ serialization: SerializationCapability<CoreWallet, null, TSerialized>;
19
+ coinsAndBalances: CoinsAndBalancesCapability<CoreWallet>;
20
+ keys: KeysCapability<CoreWallet>;
21
+ };
22
+ export type DustWalletServices = {
23
+ transactionHistory: TransactionHistoryService;
24
+ };
25
+ export declare class DustWalletState<TSerialized = string> {
26
+ static readonly mapState: <TSerialized_1 = string>(variant: DustWalletCapabilities<TSerialized_1> & DustWalletServices) => (state: ProtocolState.ProtocolState<CoreWallet>) => DustWalletState<TSerialized_1>;
27
+ readonly protocolVersion: ProtocolVersion.ProtocolVersion;
28
+ readonly state: CoreWallet;
29
+ readonly capabilities: DustWalletCapabilities<TSerialized>;
30
+ readonly services: DustWalletServices;
31
+ get totalCoins(): readonly DustFullInfo[];
32
+ get availableCoins(): readonly DustFullInfo[];
33
+ get pendingCoins(): readonly DustFullInfo[];
34
+ get publicKey(): DustPublicKey;
35
+ get address(): DustAddress;
36
+ get progress(): SyncProgress.SyncProgress;
37
+ constructor(state: ProtocolState.ProtocolState<CoreWallet>, capabilities: DustWalletCapabilities<TSerialized>, services: DustWalletServices);
38
+ balance(time: Date): Balance;
39
+ estimateDustGeneration(nightUtxos: ReadonlyArray<UtxoWithMeta>, currentTime: Date): ReadonlyArray<UtxoWithFullDustDetails>;
40
+ serialize(): TSerialized;
41
+ }
42
+ export type DustWalletAPI<TStartAux = DustSecretKey, TSerialized = string> = {
43
+ readonly state: rx.Observable<DustWalletState<TSerialized>>;
44
+ start(secretKey: TStartAux): Promise<void>;
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>;
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>;
56
+ calculateFee(transactions: ReadonlyArray<AnyTransaction>): Promise<bigint>;
57
+ estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl?: Date, currentTime?: Date): Promise<bigint>;
58
+ balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Promise<UnprovenTransaction>;
59
+ serializeState(): Promise<TSerialized>;
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>;
84
+ revertTransaction(transaction: AnyTransaction): Promise<void>;
85
+ getAddress(): Promise<DustAddress>;
86
+ stop(): Promise<void>;
87
+ };
88
+ export type CustomizedDustWallet<TStartAux = DustSecretKey, TTransaction = FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string> = DustWalletAPI<TStartAux, TSerialized> & WalletLike.WalletLike<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>]>;
89
+ export type DefaultDustConfiguration = DefaultV1Configuration;
90
+ export interface CustomizedDustWalletClass<TStartAux = DustSecretKey, TTransaction = FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV1Configuration = DefaultDustConfiguration> extends WalletLike.BaseWalletClass<[
91
+ Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>
92
+ ]> {
93
+ configuration: TConfig;
94
+ startWithSeed(seed: Uint8Array, dustParameters: DustParameters): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
95
+ startWithSecretKey(secretKey: DustSecretKey, dustParameters: DustParameters): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
96
+ restore(serializedState: TSerialized): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
97
+ }
98
+ export type DustWallet = CustomizedDustWallet<DustSecretKey, FinalizedTransaction, WalletSyncUpdate, string>;
99
+ export type DustWalletClass = CustomizedDustWalletClass<DustSecretKey, FinalizedTransaction, WalletSyncUpdate, string>;
100
+ export declare function DustWallet(configuration: DefaultDustConfiguration): DustWalletClass;
101
+ export declare function CustomDustWallet<TConfig extends BaseV1Configuration = DefaultDustConfiguration, TStartAux = DustSecretKey, TTransaction = FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string>(configuration: TConfig, builder: VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>, TConfig>): CustomizedDustWalletClass<TStartAux, TTransaction, TSyncUpdate, TSerialized, TConfig>;
@@ -0,0 +1,192 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { DustSecretKey, } from '@midnight-ntwrk/ledger-v8';
14
+ import { ProtocolVersion } from '@midnightntwrk/wallet-sdk-abstractions';
15
+ import { WalletBuilder } from '@midnightntwrk/wallet-sdk-runtime';
16
+ import { Effect, Either } from 'effect';
17
+ import * as rx from 'rxjs';
18
+ import { CoreWallet } from './v1/CoreWallet.js';
19
+ import { V1Tag } from './v1/RunningV1Variant.js';
20
+ import { V1Builder } from './v1/V1Builder.js';
21
+ export class DustWalletState {
22
+ static mapState = (variant) => (state) => {
23
+ const { serialization, coinsAndBalances, keys } = variant;
24
+ const { transactionHistory } = variant;
25
+ return new DustWalletState(state, { serialization, coinsAndBalances, keys }, { transactionHistory });
26
+ };
27
+ protocolVersion;
28
+ state;
29
+ capabilities;
30
+ services;
31
+ get totalCoins() {
32
+ return this.capabilities.coinsAndBalances.getTotalCoins(this.state);
33
+ }
34
+ get availableCoins() {
35
+ return this.capabilities.coinsAndBalances.getAvailableCoins(this.state);
36
+ }
37
+ get pendingCoins() {
38
+ return this.capabilities.coinsAndBalances.getPendingCoins(this.state);
39
+ }
40
+ get publicKey() {
41
+ return this.capabilities.keys.getPublicKey(this.state);
42
+ }
43
+ get address() {
44
+ return this.capabilities.keys.getAddress(this.state);
45
+ }
46
+ get progress() {
47
+ return this.state.progress;
48
+ }
49
+ constructor(state, capabilities, services) {
50
+ this.protocolVersion = state.version;
51
+ this.state = state.state;
52
+ this.capabilities = capabilities;
53
+ this.services = services;
54
+ }
55
+ balance(time) {
56
+ return this.capabilities.coinsAndBalances.getWalletBalance(this.state, time);
57
+ }
58
+ estimateDustGeneration(nightUtxos, currentTime) {
59
+ return this.capabilities.coinsAndBalances.estimateDustGeneration(this.state, nightUtxos, currentTime);
60
+ }
61
+ serialize() {
62
+ return this.capabilities.serialization.serialize(this.state);
63
+ }
64
+ }
65
+ export function DustWallet(configuration) {
66
+ return CustomDustWallet(configuration, new V1Builder().withDefaults());
67
+ }
68
+ export function CustomDustWallet(configuration, builder) {
69
+ const buildArgs = [configuration];
70
+ const BaseWallet = WalletBuilder.init()
71
+ .withVariant(ProtocolVersion.MinSupportedVersion, builder)
72
+ .build(...buildArgs);
73
+ return class CustomDustWalletImplementation extends BaseWallet {
74
+ static startWithSeed(seed, dustParameters) {
75
+ const dustSecretKey = DustSecretKey.fromSeed(seed);
76
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, CoreWallet.initEmpty(dustParameters, dustSecretKey, CustomDustWalletImplementation.configuration.networkId));
77
+ }
78
+ static startWithSecretKey(secretKey, dustParameters) {
79
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, CoreWallet.initEmpty(dustParameters, secretKey, CustomDustWalletImplementation.configuration.networkId));
80
+ }
81
+ static restore(serializedState) {
82
+ const deserialized = CustomDustWalletImplementation.allVariantsRecord()[V1Tag].variant.deserializeState(serializedState)
83
+ .pipe(Either.getOrThrow);
84
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, deserialized);
85
+ }
86
+ state;
87
+ constructor(runtime, scope) {
88
+ super(runtime, scope);
89
+ this.state = this.rawState.pipe(rx.map(DustWalletState.mapState(CustomDustWalletImplementation.allVariantsRecord()[V1Tag].variant)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
90
+ }
91
+ start(secretKey) {
92
+ return this.runtime.dispatch({ [V1Tag]: (v1) => v1.startSyncInBackground(secretKey) }).pipe(Effect.runPromise);
93
+ }
94
+ async createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress) {
95
+ return this.runtime
96
+ .dispatch({
97
+ [V1Tag]: (v1) => v1.createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress),
98
+ })
99
+ .pipe(Effect.runPromise);
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
+ }
115
+ addDustGenerationSignature(transaction, signature) {
116
+ return this.runtime
117
+ .dispatch({
118
+ [V1Tag]: (v1) => v1.addDustGenerationSignature(transaction, signature),
119
+ })
120
+ .pipe(Effect.runPromise);
121
+ }
122
+ addDustRegistrationSignature(transaction, signature) {
123
+ return this.runtime
124
+ .dispatch({
125
+ [V1Tag]: (v1) => v1.addDustRegistrationSignature(transaction, signature),
126
+ })
127
+ .pipe(Effect.runPromise);
128
+ }
129
+ calculateFee(transactions) {
130
+ return this.runtime
131
+ .dispatch({
132
+ [V1Tag]: (v1) => v1.calculateFee(transactions),
133
+ })
134
+ .pipe(Effect.runPromise);
135
+ }
136
+ estimateFee(secretKey, transactions, ttl, currentTime) {
137
+ const effectiveTtl = ttl ?? new Date(Date.now() + 60 * 60 * 1000);
138
+ return this.runtime
139
+ .dispatch({
140
+ [V1Tag]: (v1) => v1.estimateFee(secretKey, transactions, effectiveTtl, currentTime),
141
+ })
142
+ .pipe(Effect.runPromise);
143
+ }
144
+ balanceTransactions(secretKey, transactions, ttl, currentTime) {
145
+ return this.runtime
146
+ .dispatch({
147
+ [V1Tag]: (v1) => v1.balanceTransactions(secretKey, transactions, ttl, currentTime),
148
+ })
149
+ .pipe(Effect.runPromise);
150
+ }
151
+ revertTransaction(transaction) {
152
+ return this.runtime
153
+ .dispatch({
154
+ [V1Tag]: (v1) => v1.revertTransaction(transaction),
155
+ })
156
+ .pipe(Effect.runPromise);
157
+ }
158
+ waitForSyncedState(allowedGap = 0n) {
159
+ return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
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
+ }
185
+ serializeState() {
186
+ return rx.firstValueFrom(this.state).then((state) => state.serialize());
187
+ }
188
+ getAddress() {
189
+ return rx.firstValueFrom(this.state).then((state) => state.address);
190
+ }
191
+ };
192
+ }
@@ -0,0 +1,2 @@
1
+ export * from './DustWallet.js';
2
+ export { type DustTransactionHistoryEntry, DustSectionSchema, mergeDustSections } from './v1/TransactionHistory.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ export * from './DustWallet.js';
14
+ export { DustSectionSchema, mergeDustSections } from './v1/TransactionHistory.js';
@@ -0,0 +1,47 @@
1
+ import { type CoinRecipe } from '@midnightntwrk/wallet-sdk-capabilities';
2
+ import { type CoreWallet } from './CoreWallet.js';
3
+ import { type KeysCapability } from './Keys.js';
4
+ import { type DustGenerationDetails, type DustGenerationInfo, type Dust, type DustFullInfo, type UtxoWithMeta } from './types/Dust.js';
5
+ export type Balance = bigint;
6
+ export type CoinWithValue<TToken> = {
7
+ token: TToken;
8
+ value: Balance;
9
+ };
10
+ /**
11
+ * Type describing a Night UTxO together with details of estimated Dust generation. It is meant to be primarily used for
12
+ * fee estimation of Dust registration transaction
13
+ */
14
+ export type UtxoWithFullDustDetails = Readonly<{
15
+ utxo: UtxoWithMeta;
16
+ dust: DustGenerationDetails;
17
+ }>;
18
+ export type CoinSelection = <TCoin extends CoinRecipe>(coins: readonly TCoin[]) => TCoin | undefined;
19
+ export declare const chooseCoin: CoinSelection;
20
+ export type CoinsAndBalancesCapability<TState> = {
21
+ getWalletBalance(state: TState, time: Date): Balance;
22
+ getAvailableCoins(state: TState, time?: Date): readonly DustFullInfo[];
23
+ getPendingCoins(state: TState, time?: Date): readonly DustFullInfo[];
24
+ getTotalCoins(state: TState, time?: Date): ReadonlyArray<DustFullInfo>;
25
+ getAvailableCoinsWithGeneratedDust(state: TState, currentTime: Date): ReadonlyArray<CoinWithValue<Dust>>;
26
+ getGenerationInfo(state: TState, coin: Dust): DustGenerationInfo | undefined;
27
+ /** Splits provided Night utxos into the ones that will be used as inputs in the guaranteed and fallible sections */
28
+ splitNightUtxos(nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>): {
29
+ guaranteed: ReadonlyArray<UtxoWithFullDustDetails>;
30
+ fallible: ReadonlyArray<UtxoWithFullDustDetails>;
31
+ };
32
+ /**
33
+ * Estimate how much Dust would be available to use if the Utxos provided were used for Dust generation from their
34
+ * beginning. This function is particularly useful for the purpose of registering for Dust generation and selecting
35
+ * the Utxo to be used for paying fees and approving the registration itself.
36
+ *
37
+ * @param state Current state of the wallet
38
+ * @param nightUtxos Existing Night utxos
39
+ * @param currentTime Current time
40
+ * @returns Estimated Dust generation per Utxo
41
+ */
42
+ estimateDustGeneration(state: TState, nightUtxos: ReadonlyArray<UtxoWithMeta>, currentTime: Date): ReadonlyArray<UtxoWithFullDustDetails>;
43
+ };
44
+ export type DefaultCoinsAndBalancesContext = {
45
+ keysCapability: KeysCapability<CoreWallet>;
46
+ };
47
+ export declare const makeDefaultCoinsAndBalancesCapability: (_config: unknown, getContext: () => DefaultCoinsAndBalancesContext) => CoinsAndBalancesCapability<CoreWallet>;
@@ -0,0 +1,102 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
14
+ import { DateOps } from '@midnightntwrk/wallet-sdk-utilities';
15
+ import { pipe, Array as Arr, Order } from 'effect';
16
+ export const chooseCoin = (coins) => coins
17
+ .filter((coin) => coin.value > 0n)
18
+ .toSorted((a, b) => Number(a.value - b.value))
19
+ .at(0);
20
+ const FAKE_NONCE = '0'.repeat(64);
21
+ export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
22
+ const getWalletBalance = (state, time) => {
23
+ return state.state.walletBalance(time);
24
+ };
25
+ const getGenerationInfo = (state, coin) => {
26
+ const info = state.state.generationInfo(coin);
27
+ return info && info.dtime
28
+ ? {
29
+ ...info,
30
+ dtime: new Date(+info.dtime), // TODO: remove when the ledger start to return a date instead of the number
31
+ }
32
+ : info;
33
+ };
34
+ const resolveTime = (state, time) => time ?? state.state.syncTime;
35
+ const toFullInfo = (state, coins, time) => coins.flatMap((coin) => {
36
+ const genInfo = getGenerationInfo(state, coin);
37
+ return genInfo ? [{ token: coin, ...getFullDustInfo(state.state.params, genInfo, coin, time) }] : [];
38
+ });
39
+ const availableDustTokens = (state) => {
40
+ const pendingSpends = new Set([...state.pendingDust.values()].map((coin) => coin.nonce));
41
+ return pipe(state.state.utxos, Arr.filter((coin) => !pendingSpends.has(coin.nonce)));
42
+ };
43
+ const getAvailableCoins = (state, time) => toFullInfo(state, availableDustTokens(state), resolveTime(state, time));
44
+ const getPendingCoins = (state, time) => toFullInfo(state, state.pendingDust, resolveTime(state, time));
45
+ const getTotalCoins = (state, time) => {
46
+ const effectiveTime = resolveTime(state, time);
47
+ return [...getAvailableCoins(state, effectiveTime), ...getPendingCoins(state, effectiveTime)];
48
+ };
49
+ const getAvailableCoinsWithGeneratedDust = (state, currentTime) => getAvailableCoins(state, currentTime).map((info) => ({ token: info.token, value: info.generatedNow }));
50
+ const getFullDustInfo = (parameters, genInfo, coin, currentTime) => {
51
+ const generatedValue = ledger.updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, parameters);
52
+ return {
53
+ dtime: genInfo.dtime,
54
+ maxCap: genInfo.value * parameters.nightDustRatio,
55
+ maxCapReachedAt: DateOps.addSeconds(coin.ctime, parameters.timeToCapSeconds),
56
+ generatedNow: generatedValue,
57
+ rate: genInfo.value * parameters.generationDecayRate,
58
+ };
59
+ };
60
+ const estimateDustGeneration = (state, nightUtxos, currentTime) => {
61
+ const dustPublicKey = getContext().keysCapability.getPublicKey(state);
62
+ return pipe(nightUtxos, Arr.map((utxo) => {
63
+ const genInfo = fakeGenerationInfo(utxo, dustPublicKey);
64
+ const fakeDustCoin = fakeDustToken(dustPublicKey, utxo);
65
+ const details = getFullDustInfo(state.state.params, genInfo, fakeDustCoin, currentTime);
66
+ return { utxo, dust: details };
67
+ }));
68
+ };
69
+ /** Create a fake generation info for a given Utxo. It allows to estimate the Dust generation from it */
70
+ const fakeGenerationInfo = (utxo, dustPublicKey) => {
71
+ return {
72
+ value: utxo.value,
73
+ owner: dustPublicKey,
74
+ nonce: FAKE_NONCE,
75
+ dtime: undefined,
76
+ };
77
+ };
78
+ /** Create a fake dust coin for a given Utxo. It allows to estimate full details of the Dust generation from it */
79
+ const fakeDustToken = (dustPublicKey, utxo) => ({
80
+ initialValue: 0n,
81
+ owner: dustPublicKey,
82
+ nonce: 0n,
83
+ seq: 0,
84
+ ctime: utxo.ctime,
85
+ backingNight: '',
86
+ mtIndex: 0n,
87
+ });
88
+ const splitNightUtxos = (utxos) => {
89
+ const [guaranteed, fallible] = pipe(utxos, Arr.sort(pipe(Order.bigint, Order.reverse, Order.mapInput((coin) => coin.dust.generatedNow))), Arr.splitAt(1));
90
+ return { guaranteed, fallible };
91
+ };
92
+ return {
93
+ getWalletBalance,
94
+ getAvailableCoins,
95
+ getPendingCoins,
96
+ getTotalCoins,
97
+ getAvailableCoinsWithGeneratedDust,
98
+ getGenerationInfo,
99
+ estimateDustGeneration,
100
+ splitNightUtxos,
101
+ };
102
+ };
@@ -0,0 +1,31 @@
1
+ import { type Bindingish, DustLocalState, type DustNullifier, type DustParameters, type DustPublicKey, type DustSecretKey, type DustStateChanges, type Proofish, type Signaturish, type Transaction, type Event } from '@midnight-ntwrk/ledger-v8';
2
+ import { ProtocolVersion, SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
3
+ import { type Dust, type DustWithNullifier } from './types/Dust.js';
4
+ import { type CoinWithValue } from './CoinsAndBalances.js';
5
+ import { type NetworkId, type UnprovenDustSpend } from './types/ledger.js';
6
+ export type PublicKey = {
7
+ publicKey: DustPublicKey;
8
+ };
9
+ export declare const PublicKey: {
10
+ fromSecretKey: (secretKey: DustSecretKey) => PublicKey;
11
+ };
12
+ export type CoreWallet = Readonly<{
13
+ state: DustLocalState;
14
+ publicKey: PublicKey;
15
+ protocolVersion: ProtocolVersion.ProtocolVersion;
16
+ progress: SyncProgress.SyncProgress;
17
+ networkId: NetworkId;
18
+ pendingDust: Array<DustWithNullifier>;
19
+ }>;
20
+ export declare const CoreWallet: {
21
+ init(localState: DustLocalState, secretKey: DustSecretKey, networkId: NetworkId): CoreWallet;
22
+ initEmpty(dustParameters: DustParameters, secretKey: DustSecretKey, networkId: NetworkId): CoreWallet;
23
+ empty(localState: DustLocalState, publicKey: PublicKey, networkId: NetworkId): CoreWallet;
24
+ restore(localState: DustLocalState, publicKey: PublicKey, pendingTokens: Array<DustWithNullifier>, syncProgress: Omit<SyncProgress.SyncProgressData, "isConnected">, protocolVersion: bigint, networkId: NetworkId): CoreWallet;
25
+ applyEventsWithChanges(wallet: CoreWallet, secretKey: DustSecretKey, events: Event[], currentTime: Date): [CoreWallet, DustStateChanges[]];
26
+ applyFailed(wallet: CoreWallet, tx: Transaction<Signaturish, Proofish, Bindingish>): CoreWallet;
27
+ revertTransaction<TTransaction extends Transaction<Signaturish, Proofish, Bindingish>>(wallet: CoreWallet, tx: TTransaction): CoreWallet;
28
+ updateProgress(wallet: CoreWallet, { appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }: Partial<SyncProgress.SyncProgressData>): CoreWallet;
29
+ spendCoins(wallet: CoreWallet, secretKey: DustSecretKey, coins: ReadonlyArray<CoinWithValue<Dust>>, currentTime: Date): [ReadonlyArray<UnprovenDustSpend>, CoreWallet];
30
+ pendingDustToMap(coins: Array<DustWithNullifier>): Map<DustNullifier, Dust>;
31
+ };