@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 +106 -0
- package/dist/DustWallet.d.ts +101 -0
- package/dist/DustWallet.js +192 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/v1/CoinsAndBalances.d.ts +47 -0
- package/dist/v1/CoinsAndBalances.js +102 -0
- package/dist/v1/CoreWallet.d.ts +31 -0
- package/dist/v1/CoreWallet.js +105 -0
- package/dist/v1/Keys.d.ts +8 -0
- package/dist/v1/Keys.js +11 -0
- package/dist/v1/RunningV1Variant.d.ts +47 -0
- package/dist/v1/RunningV1Variant.js +143 -0
- package/dist/v1/Serialization.d.ts +9 -0
- package/dist/v1/Serialization.js +75 -0
- package/dist/v1/Sync.d.ts +96 -0
- package/dist/v1/Sync.js +218 -0
- package/dist/v1/Transacting.d.ts +97 -0
- package/dist/v1/Transacting.js +327 -0
- package/dist/v1/TransactionHistory.d.ts +69 -0
- package/dist/v1/TransactionHistory.js +106 -0
- package/dist/v1/Utils.d.ts +14 -0
- package/dist/v1/Utils.js +26 -0
- package/dist/v1/V1Builder.d.ts +91 -0
- package/dist/v1/V1Builder.js +166 -0
- package/dist/v1/WalletError.d.ts +43 -0
- package/dist/v1/WalletError.js +23 -0
- package/dist/v1/index.d.ts +12 -0
- package/dist/v1/index.js +24 -0
- package/dist/v1/types/Dust.d.ts +39 -0
- package/dist/v1/types/Dust.js +1 -0
- package/dist/v1/types/index.d.ts +3 -0
- package/dist/v1/types/index.js +15 -0
- package/dist/v1/types/ledger.d.ts +4 -0
- package/dist/v1/types/ledger.js +1 -0
- package/dist/v1/types/transaction.d.ts +4 -0
- package/dist/v1/types/transaction.js +1 -0
- package/package.json +57 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
};
|