@midnight-ntwrk/wallet-sdk-facade 3.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @midnight-ntwrk/wallet-sdk-facade
2
2
 
3
+ > **Note:** It is recommended to use the [`@midnight-ntwrk/wallet-sdk`](../wallet-sdk/README.md) barrel package, which
4
+ > re-exports this and all other wallet SDK packages through a single dependency.
5
+
3
6
  Unified facade for the Midnight Wallet SDK that combines all wallet types into a single API.
4
7
 
5
8
  ## Installation
@@ -21,6 +24,11 @@ The Wallet Facade provides a high-level unified interface that aggregates the fu
21
24
 
22
25
  ## Usage
23
26
 
27
+ More detailed and complete examples can be found at [docs snippets](../docs-snippets/src/snippets) (always up-to-date
28
+ with the recent changes) or at the
29
+ [SDK documentation site](https://docs.midnight.network/sdks/official/wallet-developer-guide) (aligned with the recent
30
+ release)
31
+
24
32
  ### Initializing the Facade
25
33
 
26
34
  ```typescript
package/dist/index.d.ts CHANGED
@@ -6,9 +6,72 @@ import { type AnyTransaction, type CoinsAndBalances as DustCoinsAndBalances } fr
6
6
  import { type DefaultShieldedConfiguration, type ShieldedWalletAPI, type ShieldedWalletState } from '@midnight-ntwrk/wallet-sdk-shielded';
7
7
  import type { DefaultUnshieldedConfiguration, UnshieldedWalletAPI } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
8
8
  import { type UnshieldedWalletState } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
9
+ import { Schema } from 'effect';
10
+ import { TransactionHistoryStorage } from '@midnight-ntwrk/wallet-sdk-abstractions';
9
11
  import { type Observable } from 'rxjs';
10
- import { DefaultPendingTransactionsServiceConfiguration, PendingTransactions, PendingTransactionsService } from '@midnight-ntwrk/wallet-sdk-capabilities';
11
- import { DustAddress, ShieldedAddress, UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
12
+ import { type DefaultPendingTransactionsServiceConfiguration, PendingTransactions, type PendingTransactionsService } from '@midnight-ntwrk/wallet-sdk-capabilities';
13
+ import { type DustAddress, type ShieldedAddress, type UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
14
+ /**
15
+ * Full entry schema for transaction history — common fields + all wallet sections. Pass this to
16
+ * `InMemoryTransactionHistoryStorage` to enable serialize/restore.
17
+ */
18
+ export declare const WalletEntrySchema: Schema.Struct<{
19
+ shielded: Schema.optional<Schema.Struct<{
20
+ receivedCoins: Schema.Array$<Schema.Struct<{
21
+ type: typeof Schema.String;
22
+ nonce: typeof Schema.String;
23
+ value: typeof Schema.BigInt;
24
+ mtIndex: typeof Schema.BigInt;
25
+ }>>;
26
+ spentCoins: Schema.Array$<Schema.Struct<{
27
+ type: typeof Schema.String;
28
+ nonce: typeof Schema.String;
29
+ value: typeof Schema.BigInt;
30
+ mtIndex: typeof Schema.BigInt;
31
+ }>>;
32
+ }>>;
33
+ unshielded: Schema.optional<Schema.Struct<{
34
+ id: typeof Schema.Number;
35
+ createdUtxos: Schema.Array$<Schema.Struct<{
36
+ value: Schema.Schema<bigint, string, never>;
37
+ owner: typeof Schema.String;
38
+ tokenType: typeof Schema.String;
39
+ intentHash: typeof Schema.String;
40
+ outputIndex: typeof Schema.Number;
41
+ }>>;
42
+ spentUtxos: Schema.Array$<Schema.Struct<{
43
+ value: Schema.Schema<bigint, string, never>;
44
+ owner: typeof Schema.String;
45
+ tokenType: typeof Schema.String;
46
+ intentHash: typeof Schema.String;
47
+ outputIndex: typeof Schema.Number;
48
+ }>>;
49
+ }>>;
50
+ dust: Schema.optional<Schema.Struct<{
51
+ receivedUtxos: Schema.Array$<Schema.Struct<{
52
+ initialValue: typeof Schema.BigInt;
53
+ nonce: typeof Schema.BigInt;
54
+ seq: typeof Schema.Number;
55
+ backingNight: typeof Schema.String;
56
+ mtIndex: typeof Schema.BigInt;
57
+ }>>;
58
+ spentUtxos: Schema.Array$<Schema.Struct<{
59
+ initialValue: typeof Schema.BigInt;
60
+ nonce: typeof Schema.BigInt;
61
+ seq: typeof Schema.Number;
62
+ backingNight: typeof Schema.String;
63
+ mtIndex: typeof Schema.BigInt;
64
+ }>>;
65
+ }>>;
66
+ hash: typeof Schema.String;
67
+ protocolVersion: typeof Schema.Number;
68
+ status: Schema.Literal<["SUCCESS", "FAILURE", "PARTIAL_SUCCESS"]>;
69
+ identifiers: Schema.optional<Schema.Array$<typeof Schema.String>>;
70
+ timestamp: Schema.optional<typeof Schema.Date>;
71
+ fees: Schema.optional<Schema.NullOr<typeof Schema.BigInt>>;
72
+ }>;
73
+ export type WalletEntry = Schema.Schema.Type<typeof WalletEntrySchema>;
74
+ export declare const mergeWalletEntries: (existing: WalletEntry, incoming: WalletEntry) => WalletEntry;
12
75
  type TokenKind = 'dust' | 'shielded' | 'unshielded';
13
76
  type TokenKindsToBalance = 'all' | TokenKind[];
14
77
  declare const TokenKindsToBalance: {
@@ -74,8 +137,17 @@ export declare class FacadeState {
74
137
  constructor(shielded: ShieldedWalletState, unshielded: UnshieldedWalletState, dust: DustWalletState, pending: PendingTransactions.PendingTransactions<ledger.FinalizedTransaction>);
75
138
  }
76
139
  /**
77
- * The Terms and Conditions returned by the indexer, containing a URL for display
78
- * and a SHA-256 hash for content verification.
140
+ * A clock abstraction for obtaining the current time. By default, the facade uses the system clock. For testing with a
141
+ * simulator, inject a custom clock (e.g., one backed by the simulator's time).
142
+ */
143
+ export type Clock = {
144
+ readonly now: () => Date;
145
+ };
146
+ /** Default clock using real system time. */
147
+ export declare const systemClock: Clock;
148
+ /**
149
+ * The Terms and Conditions returned by the indexer, containing a URL for display and a SHA-256 hash for content
150
+ * verification.
79
151
  */
80
152
  export type TermsAndConditions = {
81
153
  /** The hex-encoded SHA-256 hash of the Terms and Conditions document. */
@@ -84,9 +156,9 @@ export type TermsAndConditions = {
84
156
  url: string;
85
157
  };
86
158
  /**
87
- * Minimal configuration required for {@link WalletFacade.fetchTermsAndConditions}.
88
- * Accepts the shared `indexerClientConnection` sub-object found on all wallet configurations,
89
- * so callers can pass the full wallet configuration directly without any adaptation.
159
+ * Minimal configuration required for {@link WalletFacade.fetchTermsAndConditions}. Accepts the shared
160
+ * `indexerClientConnection` sub-object found on all wallet configurations, so callers can pass the full wallet
161
+ * configuration directly without any adaptation.
90
162
  */
91
163
  export type FetchTermsAndConditionsConfiguration = {
92
164
  indexerClientConnection: {
@@ -96,8 +168,16 @@ export type FetchTermsAndConditionsConfiguration = {
96
168
  };
97
169
  export type DefaultConfiguration = DefaultUnshieldedConfiguration & DefaultShieldedConfiguration & DefaultDustConfiguration & DefaultSubmissionConfiguration & DefaultPendingTransactionsServiceConfiguration & Partial<DefaultProvingConfiguration>;
98
170
  type MaybePromise<T> = T | Promise<T>;
171
+ /**
172
+ * Parameters object for {@link WalletFacade.init}. It features configuration and bunch of initializers for the wallets
173
+ * and services, all of them are in a form of a function that takes the configuration and returns proper implementation,
174
+ * either synchronously or wrapped in a Promise. Services are optional to provide ({@link WalletFacade.init} will provide
175
+ * default implementations), but all 3 wallets: shielded, unshielded and Dust one need to be present
176
+ */
99
177
  export type InitParams<TConfig extends DefaultConfiguration> = {
100
178
  configuration: TConfig;
179
+ /** Optional factory for the clock abstraction. Defaults to system clock (`() => new Date()`). */
180
+ clock?: (config: TConfig) => MaybePromise<Clock>;
101
181
  submissionService?: (config: TConfig) => MaybePromise<SubmissionService<ledger.FinalizedTransaction>>;
102
182
  pendingTransactionsService?: (config: TConfig) => MaybePromise<PendingTransactionsService<ledger.FinalizedTransaction>>;
103
183
  provingService?: (config: TConfig) => MaybePromise<ProvingService<UnboundTransaction>>;
@@ -113,20 +193,40 @@ export declare class WalletFacade {
113
193
  /**
114
194
  * Fetches the current Terms and Conditions from the network indexer.
115
195
  *
116
- * This is a static, pre-initialization utility — no wallet instance is required.
117
- * Wallet builders should call this before or independently of wallet initialization
118
- * to display the current T&C to end users and obtain the hash for content verification.
196
+ * This is a static, pre-initialization utility — no wallet instance is required. Wallet builders should call this
197
+ * before or independently of wallet initialization to display the current T&C to end users and obtain the hash for
198
+ * content verification.
119
199
  *
120
- * The returned `hash` is the hex-encoded SHA-256 hash of the document at `url`.
121
- * Wallet builders are responsible for fetching and rendering the document content
122
- * via `url` in whatever manner suits their application.
200
+ * The returned `hash` is the hex-encoded SHA-256 hash of the document at `url`. Wallet builders are responsible for
201
+ * fetching and rendering the document content via `url` in whatever manner suits their application.
123
202
  *
124
- * @param configuration - An object with an `indexerClientConnection.indexerHttpUrl`.
125
- * Any wallet configuration that satisfies {@link FetchTermsAndConditionsConfiguration} can be passed directly.
126
- * @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if
127
- * no Terms and Conditions have been set on the network yet.
203
+ * @param configuration - An object with an `indexerClientConnection.indexerHttpUrl`. Any wallet configuration that
204
+ * satisfies {@link FetchTermsAndConditionsConfiguration} can be passed directly.
205
+ * @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if no Terms and Conditions have
206
+ * been set on the network yet.
128
207
  */
129
208
  static fetchTermsAndConditions(configuration: FetchTermsAndConditionsConfiguration): Promise<TermsAndConditions>;
209
+ /**
210
+ * Default initialization for {@link WalletFacade}. It is a static method, which takes an object holding configuration
211
+ * and initialization of necessary components. Specifically - it requires following fields:
212
+ *
213
+ * - `configuration` - holding a configuration, which needs to extend {@link DefaultConfiguration} - this way allows to
214
+ * convey use-case-specific settings in the same way, as the SDK works by default
215
+ * - `shielded` - a function taking the configuration and returning shielded wallet (or a promise with such)
216
+ * implementing {@link ShieldedWalletAPI}
217
+ * - `unshielded` - a function taking the configuration and returning unshielded wallet (or a promise with such)
218
+ * implementing {@link UnshieldedWalletAPI}
219
+ * - `dust` - a function taking the configuration and returning Dust wallet (or a promise with such) implementing
220
+ * {@link DustWalletAPI} There are some optional services/abstractions to provide, too. If not provided - default
221
+ * implementations will be used, each of them is initialized by a function taking the configuration and returning
222
+ * proper implementation (wrapped in a {@link Promise} or not).
223
+ * - `submissionService` - needs to implement {@link SubmissionService} for a {@link ledger.FinalizedTransaction} to
224
+ * submit transactions to the network, default uses Node RPC connection
225
+ * - `pendingTransactionsService` - needs to implement {@link PendingTransactionsService} for a
226
+ * {@link ledger.FinalizedTransaction} to keep track of pending transactions, default uses in-memory implementation
227
+ * - `provingService` - needs to implement {@link ProvingService} to prove it, default uses proving server
228
+ * - `clock` - needs to implement {@link Clock} for getting current time, default uses system clock
229
+ */
130
230
  static init<TConfig extends DefaultConfiguration>(initParams: InitParams<TConfig>): Promise<WalletFacade>;
131
231
  readonly shielded: ShieldedWalletAPI;
132
232
  readonly unshielded: UnshieldedWalletAPI;
@@ -134,6 +234,13 @@ export declare class WalletFacade {
134
234
  readonly submissionService: SubmissionService<ledger.FinalizedTransaction>;
135
235
  readonly pendingTransactionsService: PendingTransactionsService<ledger.FinalizedTransaction>;
136
236
  readonly provingService: ProvingService<UnboundTransaction>;
237
+ readonly clock: Clock;
238
+ /**
239
+ * Constructor is private on purpose - much of initialization of the facade is potentially asynchronous, and adding
240
+ * new parameters is a breaking change to the users Use {@link WalletFacade.init} instead
241
+ *
242
+ * @private
243
+ */
137
244
  private constructor();
138
245
  private defaultTtl;
139
246
  private mergeUnprovenTransactions;
@@ -183,8 +290,10 @@ export declare class WalletFacade {
183
290
  }): Promise<UnprovenTransactionRecipe>;
184
291
  /**
185
292
  * Provides estimate of the fee of issuing registration transaction with provided UTxOs
293
+ *
186
294
  * @param nightUtxos - Night UTxOs to use for the registration
187
- * @returns And object informing about fee at the moment, as well as estimation of dust generation of the UTxO(s), that would be used for paying the fee. These include data that allows to compute when the fee could be paid
295
+ * @returns And object informing about fee at the moment, as well as estimation of dust generation of the UTxO(s),
296
+ * that would be used for paying the fee. These include data that allows to compute when the fee could be paid
188
297
  */
189
298
  estimateRegistration(nightUtxos: readonly UtxoWithMeta[]): Promise<{
190
299
  fee: bigint;
@@ -203,5 +312,7 @@ export declare class WalletFacade {
203
312
  revertTransaction(tx: AnyTransaction): Promise<void>;
204
313
  start(shieldedSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey): Promise<void>;
205
314
  stop(): Promise<void>;
315
+ queryTxHistoryByHash(hash: TransactionHistoryStorage.TransactionHash): Promise<WalletEntry | undefined>;
316
+ getAllFromTxHistory(): Promise<WalletEntry[]>;
206
317
  }
207
318
  export {};
package/dist/index.js CHANGED
@@ -13,12 +13,40 @@
13
13
  import * as ledger from '@midnight-ntwrk/ledger-v8';
14
14
  import { makeDefaultSubmissionService, } from '@midnight-ntwrk/wallet-sdk-capabilities';
15
15
  import { makeDefaultProvingService, } from '@midnight-ntwrk/wallet-sdk-capabilities/proving';
16
+ import { ShieldedSectionSchema, mergeShieldedSections, } from '@midnight-ntwrk/wallet-sdk-shielded';
17
+ import { UnshieldedSectionSchema } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
18
+ import { DustSectionSchema, mergeDustSections } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
16
19
  import { FetchTermsAndConditions as FetchTermsAndConditionsQuery } from '@midnight-ntwrk/wallet-sdk-indexer-client';
17
20
  import { QueryRunner } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
18
- import { Array as Arr, pipe } from 'effect';
21
+ import { Array as Arr, pipe, Schema } from 'effect';
22
+ import { TransactionHistoryStorage } from '@midnight-ntwrk/wallet-sdk-abstractions';
19
23
  import { combineLatest, map, firstValueFrom, concatMap } from 'rxjs';
20
24
  import { PendingTransactions, PendingTransactionsServiceImpl, } from '@midnight-ntwrk/wallet-sdk-capabilities';
21
25
  import { finalizedTransactionTrait } from './transaction.js';
26
+ /**
27
+ * Full entry schema for transaction history — common fields + all wallet sections. Pass this to
28
+ * `InMemoryTransactionHistoryStorage` to enable serialize/restore.
29
+ */
30
+ export const WalletEntrySchema = Schema.Struct({
31
+ ...TransactionHistoryStorage.TransactionHistoryCommonSchema.fields,
32
+ shielded: Schema.optional(ShieldedSectionSchema),
33
+ unshielded: Schema.optional(UnshieldedSectionSchema),
34
+ dust: Schema.optional(DustSectionSchema),
35
+ });
36
+ export const mergeWalletEntries = (existing, incoming) => ({
37
+ ...existing,
38
+ ...incoming,
39
+ ...(existing.shielded !== undefined && incoming.shielded !== undefined
40
+ ? { shielded: mergeShieldedSections(existing.shielded, incoming.shielded) }
41
+ : {}),
42
+ ...(existing.unshielded !== undefined && incoming.unshielded !== undefined
43
+ ? { unshielded: { ...existing.unshielded, ...incoming.unshielded } }
44
+ : {}),
45
+ ...(existing.dust !== undefined && incoming.dust !== undefined
46
+ ? { dust: mergeDustSections(existing.dust, incoming.dust) }
47
+ : {}),
48
+ });
49
+ const isWalletEntry = Schema.is(WalletEntrySchema);
22
50
  const TokenKindsToBalance = new (class {
23
51
  allTokenKinds = ['shielded', 'unshielded', 'dust'];
24
52
  toFlags = (tokenKinds) => {
@@ -70,6 +98,8 @@ export class FacadeState {
70
98
  }
71
99
  }
72
100
  const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
101
+ /** Default clock using real system time. */
102
+ export const systemClock = { now: () => new Date() };
73
103
  export class WalletFacade {
74
104
  static makeDefaultSubmissionService(config) {
75
105
  return makeDefaultSubmissionService(config);
@@ -93,18 +123,17 @@ export class WalletFacade {
93
123
  /**
94
124
  * Fetches the current Terms and Conditions from the network indexer.
95
125
  *
96
- * This is a static, pre-initialization utility — no wallet instance is required.
97
- * Wallet builders should call this before or independently of wallet initialization
98
- * to display the current T&C to end users and obtain the hash for content verification.
126
+ * This is a static, pre-initialization utility — no wallet instance is required. Wallet builders should call this
127
+ * before or independently of wallet initialization to display the current T&C to end users and obtain the hash for
128
+ * content verification.
99
129
  *
100
- * The returned `hash` is the hex-encoded SHA-256 hash of the document at `url`.
101
- * Wallet builders are responsible for fetching and rendering the document content
102
- * via `url` in whatever manner suits their application.
130
+ * The returned `hash` is the hex-encoded SHA-256 hash of the document at `url`. Wallet builders are responsible for
131
+ * fetching and rendering the document content via `url` in whatever manner suits their application.
103
132
  *
104
- * @param configuration - An object with an `indexerClientConnection.indexerHttpUrl`.
105
- * Any wallet configuration that satisfies {@link FetchTermsAndConditionsConfiguration} can be passed directly.
106
- * @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if
107
- * no Terms and Conditions have been set on the network yet.
133
+ * @param configuration - An object with an `indexerClientConnection.indexerHttpUrl`. Any wallet configuration that
134
+ * satisfies {@link FetchTermsAndConditionsConfiguration} can be passed directly.
135
+ * @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if no Terms and Conditions have
136
+ * been set on the network yet.
108
137
  */
109
138
  static async fetchTermsAndConditions(configuration) {
110
139
  const result = await QueryRunner.runPromise(FetchTermsAndConditionsQuery, {}, {
@@ -116,6 +145,27 @@ export class WalletFacade {
116
145
  }
117
146
  return tc;
118
147
  }
148
+ /**
149
+ * Default initialization for {@link WalletFacade}. It is a static method, which takes an object holding configuration
150
+ * and initialization of necessary components. Specifically - it requires following fields:
151
+ *
152
+ * - `configuration` - holding a configuration, which needs to extend {@link DefaultConfiguration} - this way allows to
153
+ * convey use-case-specific settings in the same way, as the SDK works by default
154
+ * - `shielded` - a function taking the configuration and returning shielded wallet (or a promise with such)
155
+ * implementing {@link ShieldedWalletAPI}
156
+ * - `unshielded` - a function taking the configuration and returning unshielded wallet (or a promise with such)
157
+ * implementing {@link UnshieldedWalletAPI}
158
+ * - `dust` - a function taking the configuration and returning Dust wallet (or a promise with such) implementing
159
+ * {@link DustWalletAPI} There are some optional services/abstractions to provide, too. If not provided - default
160
+ * implementations will be used, each of them is initialized by a function taking the configuration and returning
161
+ * proper implementation (wrapped in a {@link Promise} or not).
162
+ * - `submissionService` - needs to implement {@link SubmissionService} for a {@link ledger.FinalizedTransaction} to
163
+ * submit transactions to the network, default uses Node RPC connection
164
+ * - `pendingTransactionsService` - needs to implement {@link PendingTransactionsService} for a
165
+ * {@link ledger.FinalizedTransaction} to keep track of pending transactions, default uses in-memory implementation
166
+ * - `provingService` - needs to implement {@link ProvingService} to prove it, default uses proving server
167
+ * - `clock` - needs to implement {@link Clock} for getting current time, default uses system clock
168
+ */
119
169
  static async init(initParams) {
120
170
  const submissionService = await Promise.resolve(initParams.submissionService
121
171
  ? initParams.submissionService(initParams.configuration)
@@ -129,7 +179,8 @@ export class WalletFacade {
129
179
  const shielded = await Promise.resolve(initParams.shielded(initParams.configuration));
130
180
  const unshielded = await Promise.resolve(initParams.unshielded(initParams.configuration));
131
181
  const dust = await Promise.resolve(initParams.dust(initParams.configuration));
132
- return new WalletFacade(shielded, unshielded, dust, submissionService, pendingTransactionsService, provingService);
182
+ const clock = await Promise.resolve(initParams.clock ? initParams.clock(initParams.configuration) : systemClock);
183
+ return new WalletFacade(shielded, unshielded, dust, submissionService, pendingTransactionsService, provingService, initParams.configuration.txHistoryStorage, clock);
133
184
  }
134
185
  shielded;
135
186
  unshielded;
@@ -137,21 +188,31 @@ export class WalletFacade {
137
188
  submissionService;
138
189
  pendingTransactionsService;
139
190
  provingService;
191
+ #txHistoryStorage;
192
+ clock;
140
193
  #pendingSubscription;
141
- constructor(shieldedWallet, unshieldedWallet, dustWallet, submissionService, pendingTransactionsService, provingService) {
194
+ /**
195
+ * Constructor is private on purpose - much of initialization of the facade is potentially asynchronous, and adding
196
+ * new parameters is a breaking change to the users Use {@link WalletFacade.init} instead
197
+ *
198
+ * @private
199
+ */
200
+ constructor(shieldedWallet, unshieldedWallet, dustWallet, submissionService, pendingTransactionsService, provingService, txHistoryStorage, clock = systemClock) {
142
201
  this.shielded = shieldedWallet;
143
202
  this.unshielded = unshieldedWallet;
144
203
  this.dust = dustWallet;
145
204
  this.submissionService = submissionService;
146
205
  this.pendingTransactionsService = pendingTransactionsService;
147
206
  this.provingService = provingService;
207
+ this.#txHistoryStorage = txHistoryStorage;
208
+ this.clock = clock;
148
209
  this.#pendingSubscription = this.pendingTransactionsService
149
210
  .state()
150
211
  .pipe(concatMap((pending) => PendingTransactions.allFailed(pending)), concatMap((item) => this.revert(item.tx)))
151
212
  .subscribe();
152
213
  }
153
214
  defaultTtl() {
154
- return new Date(Date.now() + DEFAULT_TTL_MS);
215
+ return new Date(this.clock.now().getTime() + DEFAULT_TTL_MS);
155
216
  }
156
217
  mergeUnprovenTransactions(a, b) {
157
218
  if (a && b)
@@ -160,18 +221,58 @@ export class WalletFacade {
160
221
  }
161
222
  async createDustActionTransaction(action, nightUtxos, nightVerifyingKey, signDustRegistration) {
162
223
  const ttl = this.defaultTtl();
163
- const transaction = await this.dust.createDustGenerationTransaction(undefined, ttl, nightUtxos.map(({ utxo, meta }) => ({
224
+ const now = this.clock.now();
225
+ const isRegistration = action.type === 'registration';
226
+ const dustReceiverAddress = isRegistration ? action.dustReceiverAddress : undefined;
227
+ // Step 1 — Dust decides which Night UTxO belongs in the guaranteed slot (the one whose dust
228
+ // generation can pay the fee) and computes the fee-payment allowance.
229
+ const split = await this.dust.splitNightUtxosForDustRegistration(now, nightUtxos.map(({ utxo, meta }) => ({
164
230
  ...utxo,
165
231
  ctime: meta.ctime,
166
232
  registeredForDustGeneration: meta.registeredForDustGeneration,
167
- })), nightVerifyingKey, action.type === 'registration' ? action.dustReceiverAddress : undefined);
168
- const intent = transaction.intents?.get(1);
169
- if (!intent) {
170
- throw Error('Dust generation transaction is missing intent segment 1.');
233
+ })), isRegistration);
234
+ const toUnshieldedUtxoWithMeta = (u) => ({
235
+ utxo: {
236
+ value: u.utxo.value,
237
+ type: u.utxo.type,
238
+ owner: u.utxo.owner,
239
+ intentHash: u.utxo.intentHash,
240
+ outputNo: u.utxo.outputNo,
241
+ },
242
+ meta: {
243
+ ctime: u.utxo.ctime,
244
+ registeredForDustGeneration: u.utxo.registeredForDustGeneration,
245
+ },
246
+ });
247
+ const guaranteedForUnshielded = split.guaranteedUtxos.map(toUnshieldedUtxoWithMeta);
248
+ const fallibleForUnshielded = split.fallibleUtxos.map(toUnshieldedUtxoWithMeta);
249
+ // Step 2 — Unshielded books the Night UTxOs (move available -> pending) and builds the intent
250
+ // with the two offers. After this point, a concurrent build call that wants any of these UTxOs
251
+ // will fail fast.
252
+ const txWithOffers = await this.unshielded.rotateUtxos(guaranteedForUnshielded, fallibleForUnshielded, nightVerifyingKey, ttl);
253
+ // Step 3 — Dust attaches its DustActions onto the intent the unshielded wallet just built.
254
+ // If this fails we must unbook the UTxOs so the caller can retry.
255
+ let txWithDustActions;
256
+ try {
257
+ txWithDustActions = await this.dust.attachDustRegistration(txWithOffers, now, nightVerifyingKey, dustReceiverAddress, split.feePayment);
258
+ }
259
+ catch (error) {
260
+ await this.unshielded.revertTransaction(txWithOffers);
261
+ throw error;
262
+ }
263
+ // Step 4 — Sign via the standard signRecipe pathway, which now stamps both the unshielded
264
+ // offers and the dust registration. Signing failures also need to release the booking.
265
+ try {
266
+ const signedRecipe = await this.signRecipe({ type: 'UNPROVEN_TRANSACTION', transaction: txWithDustActions }, signDustRegistration);
267
+ if (signedRecipe.type !== 'UNPROVEN_TRANSACTION') {
268
+ throw Error('signRecipe returned unexpected recipe type for dust action transaction.');
269
+ }
270
+ return signedRecipe.transaction;
271
+ }
272
+ catch (error) {
273
+ await this.unshielded.revertTransaction(txWithOffers);
274
+ throw error;
171
275
  }
172
- const signatureData = intent.signatureData(1);
173
- const signature = await Promise.resolve(signDustRegistration(signatureData));
174
- return await this.dust.addDustGenerationSignature(transaction, signature);
175
276
  }
176
277
  state() {
177
278
  return combineLatest([
@@ -315,15 +416,16 @@ export class WalletFacade {
315
416
  switch (recipe.type) {
316
417
  case 'FINALIZED_TRANSACTION': {
317
418
  const signedBalancingTx = await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment);
419
+ const withDustSig = await this.#signDustRegistrationIfPresent(signedBalancingTx, signSegment);
318
420
  return {
319
421
  type: 'FINALIZED_TRANSACTION',
320
422
  originalTransaction: recipe.originalTransaction,
321
- balancingTransaction: signedBalancingTx,
423
+ balancingTransaction: withDustSig,
322
424
  };
323
425
  }
324
426
  case 'UNBOUND_TRANSACTION': {
325
427
  const signedBalancingTx = recipe.balancingTransaction
326
- ? await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment)
428
+ ? await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment).then((tx) => this.#signDustRegistrationIfPresent(tx, signSegment))
327
429
  : undefined;
328
430
  const signedBaseTx = await this.signUnboundTransaction(recipe.baseTransaction, signSegment);
329
431
  return {
@@ -334,13 +436,23 @@ export class WalletFacade {
334
436
  }
335
437
  case 'UNPROVEN_TRANSACTION': {
336
438
  const signedTx = await this.signUnprovenTransaction(recipe.transaction, signSegment);
439
+ const withDustSig = await this.#signDustRegistrationIfPresent(signedTx, signSegment);
337
440
  return {
338
441
  type: 'UNPROVEN_TRANSACTION',
339
- transaction: signedTx,
442
+ transaction: withDustSig,
340
443
  };
341
444
  }
342
445
  }
343
446
  }
447
+ async #signDustRegistrationIfPresent(tx, signSegment) {
448
+ const intent = tx.intents?.get(1);
449
+ const registrations = intent?.dustActions?.registrations ?? [];
450
+ if (!intent || registrations.length === 0) {
451
+ return tx;
452
+ }
453
+ const signature = signSegment(intent.signatureData(1));
454
+ return await this.dust.addDustRegistrationSignature(tx, signature);
455
+ }
344
456
  async signUnprovenTransaction(tx, signSegment) {
345
457
  return await this.unshielded.signUnprovenTransaction(tx, signSegment);
346
458
  }
@@ -397,11 +509,13 @@ export class WalletFacade {
397
509
  }
398
510
  /**
399
511
  * Provides estimate of the fee of issuing registration transaction with provided UTxOs
512
+ *
400
513
  * @param nightUtxos - Night UTxOs to use for the registration
401
- * @returns And object informing about fee at the moment, as well as estimation of dust generation of the UTxO(s), that would be used for paying the fee. These include data that allows to compute when the fee could be paid
514
+ * @returns And object informing about fee at the moment, as well as estimation of dust generation of the UTxO(s),
515
+ * that would be used for paying the fee. These include data that allows to compute when the fee could be paid
402
516
  */
403
517
  async estimateRegistration(nightUtxos) {
404
- const now = new Date();
518
+ const now = this.clock.now();
405
519
  const dustState = await this.dust.waitForSyncedState();
406
520
  const dustGenerationEstimations = pipe(nightUtxos, Arr.map(({ utxo, meta }) => ({
407
521
  ...utxo,
@@ -410,8 +524,23 @@ export class WalletFacade {
410
524
  })), (utxosWithMeta) => dustState.estimateDustGeneration(utxosWithMeta, now), (estimatedUtxos) => dustState.capabilities.coinsAndBalances.splitNightUtxos(estimatedUtxos), (split) => split.guaranteed);
411
525
  const fakeSigningKey = ledger.sampleSigningKey();
412
526
  const fakeVerifyingKey = ledger.signatureVerifyingKey(fakeSigningKey);
413
- const fakeRegistrationRecipe = await this.registerNightUtxosForDustGeneration(nightUtxos, fakeVerifyingKey, (payload) => ledger.signData(fakeSigningKey, payload), dustState.address);
414
- const finalizedFakeTx = fakeRegistrationRecipe.transaction.mockProve().bind();
527
+ // Use the legacy dust-only construction path here so estimation does NOT book real UTxOs in the
528
+ // unshielded wallet state. (The race-fix path in createDustActionTransaction books on purpose;
529
+ // estimation is meant to be observation-only.)
530
+ const ttl = this.defaultTtl();
531
+ const fakeUnsignedTx = await this.dust.createDustGenerationTransaction(undefined, ttl, nightUtxos.map(({ utxo, meta }) => ({
532
+ ...utxo,
533
+ ctime: meta.ctime,
534
+ registeredForDustGeneration: meta.registeredForDustGeneration,
535
+ })), fakeVerifyingKey, dustState.address);
536
+ const intent = fakeUnsignedTx.intents?.get(1);
537
+ if (!intent) {
538
+ throw Error('Dust generation transaction is missing intent segment 1.');
539
+ }
540
+ const signatureData = intent.signatureData(1);
541
+ const signature = ledger.signData(fakeSigningKey, signatureData);
542
+ const fakeSignedTx = await this.dust.addDustGenerationSignature(fakeUnsignedTx, signature);
543
+ const finalizedFakeTx = fakeSignedTx.mockProve().bind();
415
544
  const fee = await this.calculateTransactionFee(finalizedFakeTx);
416
545
  return {
417
546
  fee,
@@ -500,4 +629,12 @@ export class WalletFacade {
500
629
  Promise.resolve(this.#pendingSubscription?.unsubscribe()),
501
630
  ]);
502
631
  }
632
+ async queryTxHistoryByHash(hash) {
633
+ const raw = await this.#txHistoryStorage.get(hash);
634
+ return raw && isWalletEntry(raw) ? raw : undefined;
635
+ }
636
+ async getAllFromTxHistory() {
637
+ const allEntries = await this.#txHistoryStorage.getAll();
638
+ return [...allEntries];
639
+ }
503
640
  }
@@ -1,3 +1,3 @@
1
- import { PendingTransactions } from '@midnight-ntwrk/wallet-sdk-capabilities';
1
+ import { type PendingTransactions } from '@midnight-ntwrk/wallet-sdk-capabilities';
2
2
  import * as ledger from '@midnight-ntwrk/ledger-v8';
3
3
  export declare const finalizedTransactionTrait: PendingTransactions.TransactionTrait<ledger.FinalizedTransaction>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midnight-ntwrk/wallet-sdk-facade",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -24,19 +24,19 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@midnight-ntwrk/ledger-v8": "^8.0.2",
28
- "@midnight-ntwrk/wallet-sdk-address-format": "^3.1.0",
29
- "@midnight-ntwrk/wallet-sdk-capabilities": "^3.2.0",
30
- "@midnight-ntwrk/wallet-sdk-dust-wallet": "^3.0.0",
31
- "@midnight-ntwrk/wallet-sdk-indexer-client": "^1.2.0",
32
- "@midnight-ntwrk/wallet-sdk-shielded": "^2.1.0",
33
- "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "^2.1.0",
27
+ "@midnight-ntwrk/ledger-v8": "^8.1.0",
28
+ "@midnight-ntwrk/wallet-sdk-abstractions": "^2.1.0",
29
+ "@midnight-ntwrk/wallet-sdk-address-format": "^3.1.2",
30
+ "@midnight-ntwrk/wallet-sdk-capabilities": "^3.3.1",
31
+ "@midnight-ntwrk/wallet-sdk-dust-wallet": "^4.1.0",
32
+ "@midnight-ntwrk/wallet-sdk-indexer-client": "^1.2.2",
33
+ "@midnight-ntwrk/wallet-sdk-shielded": "^3.0.1",
34
+ "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "^3.1.0",
34
35
  "rxjs": "^7.8.2"
35
36
  },
36
37
  "devDependencies": {
37
- "@midnight-ntwrk/wallet-sdk-abstractions": "^2.0.0",
38
- "@midnight-ntwrk/wallet-sdk-hd": "^3.0.1",
39
- "@midnight-ntwrk/wallet-sdk-utilities": "1.1.0"
38
+ "@midnight-ntwrk/wallet-sdk-hd": "^3.0.2",
39
+ "@midnight-ntwrk/wallet-sdk-utilities": "^1.2.0"
40
40
  },
41
41
  "scripts": {
42
42
  "typecheck": "tsc -b ./tsconfig.json --noEmit --force",