@midnight-ntwrk/wallet-sdk-dust-wallet 3.0.0 → 4.1.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.
@@ -1,61 +1,77 @@
1
- import { type DustParameters, type DustPublicKey, DustSecretKey, type Signature, type SignatureVerifyingKey, type UnprovenTransaction } from '@midnight-ntwrk/ledger-v8';
2
- import { type ProtocolState, ProtocolVersion, SyncProgress } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
- import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
4
- import { type Variant, type WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
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 '@midnight-ntwrk/wallet-sdk-abstractions';
3
+ import { type DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
4
+ import { type Variant, type VariantBuilder, type WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
5
5
  import * as rx from 'rxjs';
6
6
  import { type Balance, type CoinsAndBalancesCapability, type UtxoWithFullDustDetails } from './v1/CoinsAndBalances.js';
7
7
  import { CoreWallet } from './v1/CoreWallet.js';
8
8
  import { type KeysCapability } from './v1/Keys.js';
9
9
  import { type SerializationCapability } from './v1/Serialization.js';
10
- import { type Dust, type DustFullInfo, type UtxoWithMeta } from './v1/types/Dust.js';
10
+ import { type NightUtxoSplitForDustRegistration } from './v1/Transacting.js';
11
+ import { type DustFullInfo, type UtxoWithMeta } from './v1/types/Dust.js';
11
12
  import { type AnyTransaction } from './v1/types/ledger.js';
12
- import { type DefaultV1Configuration, type DefaultV1Variant } from './v1/V1Builder.js';
13
- export type DustWalletCapabilities = {
14
- serialization: SerializationCapability<CoreWallet, null, string>;
13
+ import { type BaseV1Configuration, type DefaultV1Configuration, type V1Variant } from './v1/V1Builder.js';
14
+ import { type WalletSyncUpdate } from './v1/Sync.js';
15
+ import { type TransactionHistoryService } from './v1/TransactionHistory.js';
16
+ export type DustWalletCapabilities<TSerialized = string> = {
17
+ serialization: SerializationCapability<CoreWallet, null, TSerialized>;
15
18
  coinsAndBalances: CoinsAndBalancesCapability<CoreWallet>;
16
19
  keys: KeysCapability<CoreWallet>;
17
20
  };
18
- export declare class DustWalletState {
19
- static readonly mapState: (capabilities: DustWalletCapabilities) => (state: ProtocolState.ProtocolState<CoreWallet>) => DustWalletState;
21
+ export type DustWalletServices = {
22
+ transactionHistory: TransactionHistoryService;
23
+ };
24
+ export declare class DustWalletState<TSerialized = string> {
25
+ static readonly mapState: <TSerialized_1 = string>(variant: DustWalletCapabilities<TSerialized_1> & DustWalletServices) => (state: ProtocolState.ProtocolState<CoreWallet>) => DustWalletState<TSerialized_1>;
20
26
  readonly protocolVersion: ProtocolVersion.ProtocolVersion;
21
27
  readonly state: CoreWallet;
22
- readonly capabilities: DustWalletCapabilities;
23
- get totalCoins(): readonly Dust[];
24
- get availableCoins(): readonly Dust[];
25
- get pendingCoins(): readonly Dust[];
28
+ readonly capabilities: DustWalletCapabilities<TSerialized>;
29
+ readonly services: DustWalletServices;
30
+ get totalCoins(): readonly DustFullInfo[];
31
+ get availableCoins(): readonly DustFullInfo[];
32
+ get pendingCoins(): readonly DustFullInfo[];
26
33
  get publicKey(): DustPublicKey;
27
34
  get address(): DustAddress;
28
35
  get progress(): SyncProgress.SyncProgress;
29
- /**
30
- * Transaction history for the wallet.
31
- * @throws Error - Not yet implemented
32
- */
33
- get transactionHistory(): never;
34
- constructor(state: ProtocolState.ProtocolState<CoreWallet>, capabilities: DustWalletCapabilities);
36
+ constructor(state: ProtocolState.ProtocolState<CoreWallet>, capabilities: DustWalletCapabilities<TSerialized>, services: DustWalletServices);
35
37
  balance(time: Date): Balance;
36
- availableCoinsWithFullInfo(time: Date): readonly DustFullInfo[];
37
38
  estimateDustGeneration(nightUtxos: ReadonlyArray<UtxoWithMeta>, currentTime: Date): ReadonlyArray<UtxoWithFullDustDetails>;
38
- serialize(): string;
39
+ serialize(): TSerialized;
39
40
  }
40
- export type DustWalletAPI = {
41
- readonly state: rx.Observable<DustWalletState>;
42
- start(secretKey: DustSecretKey): Promise<void>;
41
+ export type DustWalletAPI<TStartAux = DustSecretKey, TSerialized = string> = {
42
+ readonly state: rx.Observable<DustWalletState<TSerialized>>;
43
+ start(secretKey: TStartAux): Promise<void>;
43
44
  createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: Array<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Promise<UnprovenTransaction>;
45
+ splitNightUtxosForDustRegistration(currentTime: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, isRegistration: boolean): Promise<NightUtxoSplitForDustRegistration>;
46
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Promise<UnprovenTransaction>;
44
47
  addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<UnprovenTransaction>;
48
+ /**
49
+ * Attaches a signature to the DustRegistration in segment 1's `dustActions` only. Unlike
50
+ * {@link addDustGenerationSignature}, this does NOT touch the unshielded offers — those should be signed separately
51
+ * via the unshielded-wallet signing path. Use this when the caller orchestrates signing across both packages (e.g.
52
+ * the facade's `signRecipe`).
53
+ */
54
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<UnprovenTransaction>;
45
55
  calculateFee(transactions: ReadonlyArray<AnyTransaction>): Promise<bigint>;
46
56
  estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl?: Date, currentTime?: Date): Promise<bigint>;
47
57
  balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Promise<UnprovenTransaction>;
48
- serializeState(): Promise<string>;
49
- waitForSyncedState(allowedGap?: bigint): Promise<DustWalletState>;
58
+ serializeState(): Promise<TSerialized>;
59
+ waitForSyncedState(allowedGap?: bigint): Promise<DustWalletState<TSerialized>>;
50
60
  revertTransaction(transaction: AnyTransaction): Promise<void>;
51
61
  getAddress(): Promise<DustAddress>;
52
62
  stop(): Promise<void>;
53
63
  };
54
- export type DustWallet = DustWalletAPI & WalletLike.WalletLike<[Variant.VersionedVariant<DefaultV1Variant>]>;
55
- export interface DustWalletClass extends WalletLike.BaseWalletClass<[Variant.VersionedVariant<DefaultV1Variant>]> {
56
- startWithSeed(seed: Uint8Array, dustParameters: DustParameters): DustWallet;
57
- startWithSecretKey(secretKey: DustSecretKey, dustParameters: DustParameters): DustWallet;
58
- restore(serializedState: string): DustWallet;
59
- }
64
+ export type CustomizedDustWallet<TStartAux = DustSecretKey, TTransaction = FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string> = DustWalletAPI<TStartAux, TSerialized> & WalletLike.WalletLike<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>]>;
60
65
  export type DefaultDustConfiguration = DefaultV1Configuration;
66
+ export interface CustomizedDustWalletClass<TStartAux = DustSecretKey, TTransaction = FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV1Configuration = DefaultDustConfiguration> extends WalletLike.BaseWalletClass<[
67
+ Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>
68
+ ]> {
69
+ configuration: TConfig;
70
+ startWithSeed(seed: Uint8Array, dustParameters: DustParameters): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
71
+ startWithSecretKey(secretKey: DustSecretKey, dustParameters: DustParameters): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
72
+ restore(serializedState: TSerialized): CustomizedDustWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
73
+ }
74
+ export type DustWallet = CustomizedDustWallet<DustSecretKey, FinalizedTransaction, WalletSyncUpdate, string>;
75
+ export type DustWalletClass = CustomizedDustWalletClass<DustSecretKey, FinalizedTransaction, WalletSyncUpdate, string>;
61
76
  export declare function DustWallet(configuration: DefaultDustConfiguration): DustWalletClass;
77
+ 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>;
@@ -19,12 +19,15 @@ import { CoreWallet } from './v1/CoreWallet.js';
19
19
  import { V1Tag } from './v1/RunningV1Variant.js';
20
20
  import { V1Builder } from './v1/V1Builder.js';
21
21
  export class DustWalletState {
22
- static mapState = (capabilities) => (state) => {
23
- return new DustWalletState(state, capabilities);
22
+ static mapState = (variant) => (state) => {
23
+ const { serialization, coinsAndBalances, keys } = variant;
24
+ const { transactionHistory } = variant;
25
+ return new DustWalletState(state, { serialization, coinsAndBalances, keys }, { transactionHistory });
24
26
  };
25
27
  protocolVersion;
26
28
  state;
27
29
  capabilities;
30
+ services;
28
31
  get totalCoins() {
29
32
  return this.capabilities.coinsAndBalances.getTotalCoins(this.state);
30
33
  }
@@ -43,24 +46,15 @@ export class DustWalletState {
43
46
  get progress() {
44
47
  return this.state.progress;
45
48
  }
46
- /**
47
- * Transaction history for the wallet.
48
- * @throws Error - Not yet implemented
49
- */
50
- get transactionHistory() {
51
- throw new Error('Transaction history is not yet implemented for DustWallet');
52
- }
53
- constructor(state, capabilities) {
49
+ constructor(state, capabilities, services) {
54
50
  this.protocolVersion = state.version;
55
51
  this.state = state.state;
56
52
  this.capabilities = capabilities;
53
+ this.services = services;
57
54
  }
58
55
  balance(time) {
59
56
  return this.capabilities.coinsAndBalances.getWalletBalance(this.state, time);
60
57
  }
61
- availableCoinsWithFullInfo(time) {
62
- return this.capabilities.coinsAndBalances.getAvailableCoinsWithFullInfo(this.state, time);
63
- }
64
58
  estimateDustGeneration(nightUtxos, currentTime) {
65
59
  return this.capabilities.coinsAndBalances.estimateDustGeneration(this.state, nightUtxos, currentTime);
66
60
  }
@@ -69,26 +63,30 @@ export class DustWalletState {
69
63
  }
70
64
  }
71
65
  export function DustWallet(configuration) {
66
+ return CustomDustWallet(configuration, new V1Builder().withDefaults());
67
+ }
68
+ export function CustomDustWallet(configuration, builder) {
69
+ const buildArgs = [configuration];
72
70
  const BaseWallet = WalletBuilder.init()
73
- .withVariant(ProtocolVersion.MinSupportedVersion, new V1Builder().withDefaults())
74
- .build(configuration);
75
- return class DustWalletImplementation extends BaseWallet {
71
+ .withVariant(ProtocolVersion.MinSupportedVersion, builder)
72
+ .build(...buildArgs);
73
+ return class CustomDustWalletImplementation extends BaseWallet {
76
74
  static startWithSeed(seed, dustParameters) {
77
75
  const dustSecretKey = DustSecretKey.fromSeed(seed);
78
- return DustWalletImplementation.startFirst(DustWalletImplementation, CoreWallet.initEmpty(dustParameters, dustSecretKey, configuration.networkId));
76
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, CoreWallet.initEmpty(dustParameters, dustSecretKey, CustomDustWalletImplementation.configuration.networkId));
79
77
  }
80
78
  static startWithSecretKey(secretKey, dustParameters) {
81
- return DustWalletImplementation.startFirst(DustWalletImplementation, CoreWallet.initEmpty(dustParameters, secretKey, configuration.networkId));
79
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, CoreWallet.initEmpty(dustParameters, secretKey, CustomDustWalletImplementation.configuration.networkId));
82
80
  }
83
81
  static restore(serializedState) {
84
- const deserialized = DustWalletImplementation.allVariantsRecord()[V1Tag].variant.deserializeState(serializedState)
82
+ const deserialized = CustomDustWalletImplementation.allVariantsRecord()[V1Tag].variant.deserializeState(serializedState)
85
83
  .pipe(Either.getOrThrow);
86
- return DustWalletImplementation.startFirst(DustWalletImplementation, deserialized);
84
+ return CustomDustWalletImplementation.startFirst(CustomDustWalletImplementation, deserialized);
87
85
  }
88
86
  state;
89
87
  constructor(runtime, scope) {
90
88
  super(runtime, scope);
91
- this.state = this.rawState.pipe(rx.map(DustWalletState.mapState(DustWalletImplementation.allVariantsRecord()[V1Tag].variant)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
89
+ this.state = this.rawState.pipe(rx.map(DustWalletState.mapState(CustomDustWalletImplementation.allVariantsRecord()[V1Tag].variant)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
92
90
  }
93
91
  start(secretKey) {
94
92
  return this.runtime.dispatch({ [V1Tag]: (v1) => v1.startSyncInBackground(secretKey) }).pipe(Effect.runPromise);
@@ -100,6 +98,20 @@ export function DustWallet(configuration) {
100
98
  })
101
99
  .pipe(Effect.runPromise);
102
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
+ }
103
115
  addDustGenerationSignature(transaction, signature) {
104
116
  return this.runtime
105
117
  .dispatch({
@@ -107,6 +119,13 @@ export function DustWallet(configuration) {
107
119
  })
108
120
  .pipe(Effect.runPromise);
109
121
  }
122
+ addDustRegistrationSignature(transaction, signature) {
123
+ return this.runtime
124
+ .dispatch({
125
+ [V1Tag]: (v1) => v1.addDustRegistrationSignature(transaction, signature),
126
+ })
127
+ .pipe(Effect.runPromise);
128
+ }
110
129
  calculateFee(transactions) {
111
130
  return this.runtime
112
131
  .dispatch({
@@ -139,10 +158,6 @@ export function DustWallet(configuration) {
139
158
  waitForSyncedState(allowedGap = 0n) {
140
159
  return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
141
160
  }
142
- /**
143
- * Serializes the most recent state
144
- * It's preferable to use [[DustWalletState.serialize]] instead, to know exactly, which state is serialized
145
- */
146
161
  serializeState() {
147
162
  return rx.firstValueFrom(this.state).then((state) => state.serialize());
148
163
  }
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './DustWallet.js';
2
+ export { type DustTransactionHistoryEntry, DustSectionSchema, mergeDustSections } from './v1/TransactionHistory.js';
package/dist/index.js CHANGED
@@ -11,3 +11,4 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  export * from './DustWallet.js';
14
+ export { DustSectionSchema, mergeDustSections } from './v1/TransactionHistory.js';
@@ -1,39 +1,39 @@
1
- import { CoreWallet } from './CoreWallet.js';
2
- import { KeysCapability } from './Keys.js';
3
- import { DustGenerationDetails, DustGenerationInfo, Dust, DustFullInfo, UtxoWithMeta } from './types/Dust.js';
1
+ import { type CoinRecipe } from '@midnight-ntwrk/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';
4
5
  export type Balance = bigint;
5
6
  export type CoinWithValue<TToken> = {
6
7
  token: TToken;
7
8
  value: Balance;
8
9
  };
9
10
  /**
10
- * Type describing a Night UTxO together with details of estimated Dust generation.
11
- * It is meant to be primarily used for fee estimation of Dust registration transaction
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
12
13
  */
13
14
  export type UtxoWithFullDustDetails = Readonly<{
14
15
  utxo: UtxoWithMeta;
15
16
  dust: DustGenerationDetails;
16
17
  }>;
17
- export type CoinSelection<TInput> = (coins: readonly CoinWithValue<TInput>[]) => CoinWithValue<TInput> | undefined;
18
- export declare const chooseCoin: <TInput>(coins: readonly CoinWithValue<TInput>[]) => CoinWithValue<TInput> | undefined;
18
+ export type CoinSelection = <TCoin extends CoinRecipe>(coins: readonly TCoin[]) => TCoin | undefined;
19
+ export declare const chooseCoin: CoinSelection;
19
20
  export type CoinsAndBalancesCapability<TState> = {
20
21
  getWalletBalance(state: TState, time: Date): Balance;
21
- getAvailableCoins(state: TState): readonly Dust[];
22
- getPendingCoins(state: TState): readonly Dust[];
23
- getTotalCoins(state: TState): ReadonlyArray<Dust>;
22
+ getAvailableCoins(state: TState, time?: Date): readonly DustFullInfo[];
23
+ getPendingCoins(state: TState, time?: Date): readonly DustFullInfo[];
24
+ getTotalCoins(state: TState, time?: Date): ReadonlyArray<DustFullInfo>;
24
25
  getAvailableCoinsWithGeneratedDust(state: TState, currentTime: Date): ReadonlyArray<CoinWithValue<Dust>>;
25
- getAvailableCoinsWithFullInfo(state: TState, blockTime: Date): readonly DustFullInfo[];
26
26
  getGenerationInfo(state: TState, coin: Dust): DustGenerationInfo | undefined;
27
- /**
28
- * Splits provided Night utxos into the ones that will be used as inputs in the guaranteed and fallible sections
29
- */
27
+ /** Splits provided Night utxos into the ones that will be used as inputs in the guaranteed and fallible sections */
30
28
  splitNightUtxos(nightUtxos: ReadonlyArray<UtxoWithFullDustDetails>): {
31
29
  guaranteed: ReadonlyArray<UtxoWithFullDustDetails>;
32
30
  fallible: ReadonlyArray<UtxoWithFullDustDetails>;
33
31
  };
34
32
  /**
35
- * Estimate how much Dust would be available to use if the Utxos provided were used for Dust generation from their beginning.
36
- * 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
+ * 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
37
  * @param state Current state of the wallet
38
38
  * @param nightUtxos Existing Night utxos
39
39
  * @param currentTime Current time
@@ -13,18 +13,15 @@
13
13
  import * as ledger from '@midnight-ntwrk/ledger-v8';
14
14
  import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
15
15
  import { pipe, Array as Arr, Order } from 'effect';
16
- export const chooseCoin = (coins) => coins.toSorted((a, b) => Number(a.value - b.value)).at(0);
16
+ export const chooseCoin = (coins) => coins
17
+ .filter((coin) => coin.value > 0n)
18
+ .toSorted((a, b) => Number(a.value - b.value))
19
+ .at(0);
17
20
  const FAKE_NONCE = '0'.repeat(64);
18
21
  export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
19
22
  const getWalletBalance = (state, time) => {
20
23
  return state.state.walletBalance(time);
21
24
  };
22
- const getAvailableCoins = (state) => {
23
- const pendingSpends = new Set([...state.pendingDust.values()].map((coin) => coin.nonce));
24
- return pipe(state.state.utxos, Arr.filter((coin) => !pendingSpends.has(coin.nonce)));
25
- };
26
- const getPendingCoins = (state) => state.pendingDust;
27
- const getTotalCoins = (state) => [...getAvailableCoins(state), ...getPendingCoins(state)];
28
25
  const getGenerationInfo = (state, coin) => {
29
26
  const info = state.state.generationInfo(coin);
30
27
  return info && info.dtime
@@ -34,32 +31,22 @@ export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
34
31
  }
35
32
  : info;
36
33
  };
37
- const getAvailableCoinsWithGeneratedDust = (state, currentTime) => {
38
- const result = [];
39
- const available = getAvailableCoins(state);
40
- for (const coin of available) {
41
- const genInfo = getGenerationInfo(state, coin);
42
- if (genInfo) {
43
- const generatedValue = ledger.updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, state.state.params);
44
- result.push({ token: coin, value: generatedValue });
45
- }
46
- }
47
- return result;
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)));
48
42
  };
49
- const getAvailableCoinsWithFullInfo = (state, blockTime) => {
50
- const result = [];
51
- const available = getAvailableCoins(state);
52
- for (const coin of available) {
53
- const genInfo = getGenerationInfo(state, coin);
54
- if (genInfo) {
55
- result.push({
56
- token: coin,
57
- ...getFullDustInfo(state.state.params, genInfo, coin, blockTime),
58
- });
59
- }
60
- }
61
- return result;
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)];
62
48
  };
49
+ const getAvailableCoinsWithGeneratedDust = (state, currentTime) => getAvailableCoins(state, currentTime).map((info) => ({ token: info.token, value: info.generatedNow }));
63
50
  const getFullDustInfo = (parameters, genInfo, coin, currentTime) => {
64
51
  const generatedValue = ledger.updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, parameters);
65
52
  return {
@@ -79,9 +66,7 @@ export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
79
66
  return { utxo, dust: details };
80
67
  }));
81
68
  };
82
- /**
83
- * Create a fake generation info for a given Utxo. It allows to estimate the Dust generation from it
84
- */
69
+ /** Create a fake generation info for a given Utxo. It allows to estimate the Dust generation from it */
85
70
  const fakeGenerationInfo = (utxo, dustPublicKey) => {
86
71
  return {
87
72
  value: utxo.value,
@@ -90,9 +75,7 @@ export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
90
75
  dtime: undefined,
91
76
  };
92
77
  };
93
- /**
94
- * Create a fake dust coin for a given Utxo. It allows to estimate full details of the Dust generation from it
95
- */
78
+ /** Create a fake dust coin for a given Utxo. It allows to estimate full details of the Dust generation from it */
96
79
  const fakeDustToken = (dustPublicKey, utxo) => ({
97
80
  initialValue: 0n,
98
81
  owner: dustPublicKey,
@@ -112,7 +95,6 @@ export const makeDefaultCoinsAndBalancesCapability = (_config, getContext) => {
112
95
  getPendingCoins,
113
96
  getTotalCoins,
114
97
  getAvailableCoinsWithGeneratedDust,
115
- getAvailableCoinsWithFullInfo,
116
98
  getGenerationInfo,
117
99
  estimateDustGeneration,
118
100
  splitNightUtxos,
@@ -1,8 +1,8 @@
1
- import { Bindingish, DustLocalState, DustNullifier, DustParameters, DustPublicKey, DustSecretKey, Proofish, Signaturish, Transaction, Event } from '@midnight-ntwrk/ledger-v8';
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
2
  import { ProtocolVersion, SyncProgress } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
- import { Dust, DustWithNullifier } from './types/Dust.js';
4
- import { CoinWithValue } from './CoinsAndBalances.js';
5
- import { NetworkId, UnprovenDustSpend } from './types/ledger.js';
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
6
  export type PublicKey = {
7
7
  publicKey: DustPublicKey;
8
8
  };
@@ -22,7 +22,7 @@ export declare const CoreWallet: {
22
22
  initEmpty(dustParameters: DustParameters, secretKey: DustSecretKey, networkId: NetworkId): CoreWallet;
23
23
  empty(localState: DustLocalState, publicKey: PublicKey, networkId: NetworkId): CoreWallet;
24
24
  restore(localState: DustLocalState, publicKey: PublicKey, pendingTokens: Array<DustWithNullifier>, syncProgress: Omit<SyncProgress.SyncProgressData, "isConnected">, protocolVersion: bigint, networkId: NetworkId): CoreWallet;
25
- applyEvents(wallet: CoreWallet, secretKey: DustSecretKey, events: Event[], currentTime: Date): CoreWallet;
25
+ applyEventsWithChanges(wallet: CoreWallet, secretKey: DustSecretKey, events: Event[], currentTime: Date): [CoreWallet, DustStateChanges[]];
26
26
  applyFailed(wallet: CoreWallet, tx: Transaction<Signaturish, Proofish, Bindingish>): CoreWallet;
27
27
  revertTransaction<TTransaction extends Transaction<Signaturish, Proofish, Bindingish>>(wallet: CoreWallet, tx: TTransaction): CoreWallet;
28
28
  updateProgress(wallet: CoreWallet, { appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }: Partial<SyncProgress.SyncProgressData>): CoreWallet;
@@ -48,15 +48,19 @@ export const CoreWallet = {
48
48
  protocolVersion: ProtocolVersion.ProtocolVersion(protocolVersion),
49
49
  };
50
50
  },
51
- applyEvents(wallet, secretKey, events, currentTime) {
51
+ applyEventsWithChanges(wallet, secretKey, events, currentTime) {
52
52
  // TODO: replace currentTime with `updatedState.syncTime` introduced in ledger-6.2.0-rc.1
53
- const updatedState = wallet.state.replayEvents(secretKey, events).processTtls(currentTime);
53
+ const stateWithChanges = wallet.state.replayEventsWithChanges(secretKey, events);
54
+ const updatedState = stateWithChanges.state.processTtls(currentTime);
54
55
  const availableNonces = updatedState.utxos.map((utxo) => utxo.nonce);
55
- return {
56
- ...wallet,
57
- state: updatedState,
58
- pendingDust: wallet.pendingDust.filter((t) => availableNonces.includes(t.nonce)),
59
- };
56
+ return [
57
+ {
58
+ ...wallet,
59
+ state: updatedState,
60
+ pendingDust: wallet.pendingDust.filter((t) => availableNonces.includes(t.nonce)),
61
+ },
62
+ stateWithChanges.changes,
63
+ ];
60
64
  },
61
65
  applyFailed(wallet, tx) {
62
66
  const pendingSpendsMap = CoreWallet.pendingDustToMap(wallet.pendingDust);
package/dist/v1/Keys.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { DustPublicKey } from '@midnight-ntwrk/ledger-v8';
1
+ import { type DustPublicKey } from '@midnight-ntwrk/ledger-v8';
2
2
  import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
3
- import { CoreWallet } from './CoreWallet.js';
3
+ import { type CoreWallet } from './CoreWallet.js';
4
4
  export type KeysCapability<TState> = {
5
5
  getPublicKey(state: TState): DustPublicKey;
6
6
  getAddress(state: TState): DustAddress;
@@ -1,26 +1,28 @@
1
1
  import { Effect, Stream, Scope } from 'effect';
2
+ import { type TransactionHistoryService } from './TransactionHistory.js';
2
3
  import { type DustSecretKey, type Signature, type SignatureVerifyingKey, type FinalizedTransaction, type UnprovenTransaction } from '@midnight-ntwrk/ledger-v8';
3
- import { WalletError } from './WalletError.js';
4
+ import { type WalletError } from './WalletError.js';
4
5
  import { type WalletRuntimeError, type Variant, StateChange } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
5
- import { type Dust, type UtxoWithMeta } from './types/Dust.js';
6
+ import { type UtxoWithMeta } from './types/Dust.js';
6
7
  import { type KeysCapability } from './Keys.js';
7
- import { type SyncCapability, type SyncService } from './Sync.js';
8
- import { type SimulatorState } from './Simulator.js';
8
+ import { type ChangesResult, type SyncCapability, type SyncService } from './Sync.js';
9
+ import { type SimulatorState } from '@midnight-ntwrk/wallet-sdk-capabilities/simulation';
9
10
  import { type CoinsAndBalancesCapability, type CoinSelection } from './CoinsAndBalances.js';
10
- import { type TransactingCapability } from './Transacting.js';
11
+ import { type NightUtxoSplitForDustRegistration, type TransactingCapability } from './Transacting.js';
11
12
  import { type CoreWallet } from './CoreWallet.js';
12
13
  import { type SerializationCapability } from './Serialization.js';
13
14
  import { type AnyTransaction } from './types/ledger.js';
14
- import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
15
+ import { type DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
15
16
  export declare namespace RunningV1Variant {
16
17
  type Context<TSerialized, TSyncUpdate, TTransaction, TStartAux> = {
17
18
  serializationCapability: SerializationCapability<CoreWallet, null, TSerialized>;
18
19
  syncService: SyncService<CoreWallet, TStartAux, TSyncUpdate>;
19
- syncCapability: SyncCapability<CoreWallet, TSyncUpdate>;
20
+ syncCapability: SyncCapability<CoreWallet, TSyncUpdate, ChangesResult>;
20
21
  transactingCapability: TransactingCapability<DustSecretKey, CoreWallet, TTransaction>;
21
22
  coinsAndBalancesCapability: CoinsAndBalancesCapability<CoreWallet>;
22
23
  keysCapability: KeysCapability<CoreWallet>;
23
- coinSelection: CoinSelection<Dust>;
24
+ coinSelection: CoinSelection;
25
+ transactionHistoryService: TransactionHistoryService;
24
26
  };
25
27
  type AnyContext = Context<any, any, any, any>;
26
28
  }
@@ -34,7 +36,10 @@ export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction, TS
34
36
  startSyncInBackground(startAux: TStartAux): Effect.Effect<void>;
35
37
  startSync(startAux: TStartAux): Stream.Stream<void, WalletError, Scope.Scope>;
36
38
  createDustGenerationTransaction(currentTime: Date | undefined, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined): Effect.Effect<UnprovenTransaction, WalletError>;
39
+ splitNightUtxosForDustRegistration(currentTime: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, isRegistration: boolean): Effect.Effect<NightUtxoSplitForDustRegistration, WalletError>;
40
+ attachDustRegistration(transaction: UnprovenTransaction, currentTime: Date, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: DustAddress | undefined, feePayment: bigint): Effect.Effect<UnprovenTransaction, WalletError>;
37
41
  addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
42
+ addDustRegistrationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<UnprovenTransaction, WalletError>;
38
43
  calculateFee(transactions: ReadonlyArray<AnyTransaction>): Effect.Effect<bigint, WalletError>;
39
44
  estimateFee(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<bigint, WalletError>;
40
45
  balanceTransactions(secretKey: DustSecretKey, transactions: ReadonlyArray<AnyTransaction>, ttl: Date, currentTime?: Date): Effect.Effect<UnprovenTransaction, WalletError>;
@@ -62,15 +62,16 @@ export class RunningV1Variant {
62
62
  return this.startSync(startAux).pipe(Stream.runScoped(Sink.drain), Effect.forkScoped, Effect.provideService(Scope.Scope, this.#scope));
63
63
  }
64
64
  startSync(startAux) {
65
- return pipe(SubscriptionRef.get(this.#context.stateRef), Stream.fromEffect, Stream.flatMap((state) => this.#v1Context.syncService.updates(state, startAux)), Stream.mapEffect((update) => {
66
- return SubscriptionRef.updateEffect(this.#context.stateRef, (state) => Effect.try({
67
- try: () => this.#v1Context.syncCapability.applyUpdate(state, update),
68
- catch: (err) => new OtherWalletError({
69
- message: 'Error while applying sync update',
70
- cause: err,
71
- }),
72
- }));
73
- }), Stream.tapError((error) => Console.error(error)), Stream.retry(pipe(Schedule.exponential(Duration.seconds(1), 2), Schedule.map((delay) => {
65
+ return pipe(SubscriptionRef.get(this.#context.stateRef), Stream.fromEffect, Stream.flatMap((state) => this.#v1Context.syncService.updates(state, startAux)), Stream.mapEffect((update) => SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => Effect.try({
66
+ try: () => {
67
+ const [newState, changesResult] = this.#v1Context.syncCapability.applyUpdate(state, update);
68
+ return [changesResult, newState];
69
+ },
70
+ catch: (err) => new OtherWalletError({
71
+ message: 'Error while applying sync update',
72
+ cause: err,
73
+ }),
74
+ })).pipe(Effect.flatMap(({ changes, protocolVersion }) => pipe(Effect.forEach(changes, (change) => pipe(this.#v1Context.transactionHistoryService.getTransactionDetails(change.source), Effect.flatMap((metadata) => this.#v1Context.transactionHistoryService.put(change, metadata, protocolVersion)), Effect.catchAllCause((cause) => Console.error('Error processing tx history metadata', cause))), { discard: true, concurrency: 'unbounded' }), Effect.forkScoped)), Effect.provideService(Scope.Scope, this.#scope))), Stream.tapError((error) => Console.error(error)), Stream.retry(pipe(Schedule.exponential(Duration.seconds(1), 2), Schedule.map((delay) => {
74
75
  const maxDelay = Duration.minutes(2);
75
76
  const jitter = Duration.millis(Math.floor(Math.random() * 1000));
76
77
  const delayWithJitter = Duration.toMillis(delay) + Duration.toMillis(jitter);
@@ -81,19 +82,41 @@ export class RunningV1Variant {
81
82
  if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
82
83
  return Effect.fail(new OtherWalletError({ message: 'Token of a non-Night type received' }));
83
84
  }
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 }) => {
87
- return this.#v1Context.transactingCapability
88
- .createDustGenerationTransaction(currentTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
85
+ return Effect.gen(this, function* () {
86
+ const currentState = yield* SubscriptionRef.get(this.#context.stateRef);
87
+ const blockData = yield* this.#v1Context.syncService.blockData();
88
+ const resolvedTime = currentTime ?? blockData.timestamp;
89
+ const utxosWithDustValue = this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, resolvedTime);
90
+ return yield* this.#v1Context.transactingCapability
91
+ .createDustGenerationTransaction(resolvedTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
89
92
  .pipe(EitherOps.toEffect);
90
- }));
93
+ });
94
+ }
95
+ splitNightUtxosForDustRegistration(currentTime, nightUtxos, isRegistration) {
96
+ if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
97
+ return Effect.fail(new OtherWalletError({ message: 'Token of a non-Night type received' }));
98
+ }
99
+ return Effect.gen(this, function* () {
100
+ const currentState = yield* SubscriptionRef.get(this.#context.stateRef);
101
+ const utxosWithDustValue = this.#v1Context.coinsAndBalancesCapability.estimateDustGeneration(currentState, nightUtxos, currentTime);
102
+ return this.#v1Context.transactingCapability.splitNightUtxosForDustRegistration(utxosWithDustValue, isRegistration);
103
+ });
104
+ }
105
+ attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment) {
106
+ return this.#v1Context.transactingCapability
107
+ .attachDustRegistration(transaction, currentTime, nightVerifyingKey, dustReceiverAddress, feePayment)
108
+ .pipe(EitherOps.toEffect);
91
109
  }
92
110
  addDustGenerationSignature(transaction, signature) {
93
111
  return this.#v1Context.transactingCapability
94
112
  .addDustGenerationSignature(transaction, signature)
95
113
  .pipe(EitherOps.toEffect);
96
114
  }
115
+ addDustRegistrationSignature(transaction, signature) {
116
+ return this.#v1Context.transactingCapability
117
+ .addDustRegistrationSignature(transaction, signature)
118
+ .pipe(EitherOps.toEffect);
119
+ }
97
120
  calculateFee(transactions) {
98
121
  return pipe(this.#v1Context.syncService.blockData(), Effect.map((blockData) => pipe(transactions, Arr.map((transaction) => this.#v1Context.transactingCapability.calculateFee(transaction, blockData.ledgerParameters)), ArrayOps.sumBigInt)));
99
122
  }
@@ -1,5 +1,5 @@
1
1
  import { Either, Schema } from 'effect';
2
- import { WalletError } from './WalletError.js';
2
+ import { type WalletError } from './WalletError.js';
3
3
  import { CoreWallet } from './CoreWallet.js';
4
4
  export type SerializationCapability<TWallet, TAux, TSerialized> = {
5
5
  serialize(wallet: TWallet): TSerialized;