@midnight-ntwrk/wallet-sdk-facade 4.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
@@ -24,6 +24,11 @@ The Wallet Facade provides a high-level unified interface that aggregates the fu
24
24
 
25
25
  ## Usage
26
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
+
27
32
  ### Initializing the Facade
28
33
 
29
34
  ```typescript
package/dist/index.d.ts CHANGED
@@ -168,6 +168,12 @@ export type FetchTermsAndConditionsConfiguration = {
168
168
  };
169
169
  export type DefaultConfiguration = DefaultUnshieldedConfiguration & DefaultShieldedConfiguration & DefaultDustConfiguration & DefaultSubmissionConfiguration & DefaultPendingTransactionsServiceConfiguration & Partial<DefaultProvingConfiguration>;
170
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
+ */
171
177
  export type InitParams<TConfig extends DefaultConfiguration> = {
172
178
  configuration: TConfig;
173
179
  /** Optional factory for the clock abstraction. Defaults to system clock (`() => new Date()`). */
@@ -200,6 +206,27 @@ export declare class WalletFacade {
200
206
  * been set on the network yet.
201
207
  */
202
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
+ */
203
230
  static init<TConfig extends DefaultConfiguration>(initParams: InitParams<TConfig>): Promise<WalletFacade>;
204
231
  readonly shielded: ShieldedWalletAPI;
205
232
  readonly unshielded: UnshieldedWalletAPI;
@@ -208,6 +235,12 @@ export declare class WalletFacade {
208
235
  readonly pendingTransactionsService: PendingTransactionsService<ledger.FinalizedTransaction>;
209
236
  readonly provingService: ProvingService<UnboundTransaction>;
210
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
+ */
211
244
  private constructor();
212
245
  private defaultTtl;
213
246
  private mergeUnprovenTransactions;
package/dist/index.js CHANGED
@@ -145,6 +145,27 @@ export class WalletFacade {
145
145
  }
146
146
  return tc;
147
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
+ */
148
169
  static async init(initParams) {
149
170
  const submissionService = await Promise.resolve(initParams.submissionService
150
171
  ? initParams.submissionService(initParams.configuration)
@@ -170,6 +191,12 @@ export class WalletFacade {
170
191
  #txHistoryStorage;
171
192
  clock;
172
193
  #pendingSubscription;
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
+ */
173
200
  constructor(shieldedWallet, unshieldedWallet, dustWallet, submissionService, pendingTransactionsService, provingService, txHistoryStorage, clock = systemClock) {
174
201
  this.shielded = shieldedWallet;
175
202
  this.unshielded = unshieldedWallet;
@@ -194,18 +221,58 @@ export class WalletFacade {
194
221
  }
195
222
  async createDustActionTransaction(action, nightUtxos, nightVerifyingKey, signDustRegistration) {
196
223
  const ttl = this.defaultTtl();
197
- 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 }) => ({
198
230
  ...utxo,
199
231
  ctime: meta.ctime,
200
232
  registeredForDustGeneration: meta.registeredForDustGeneration,
201
- })), nightVerifyingKey, action.type === 'registration' ? action.dustReceiverAddress : undefined);
202
- const intent = transaction.intents?.get(1);
203
- if (!intent) {
204
- 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;
205
275
  }
206
- const signatureData = intent.signatureData(1);
207
- const signature = await Promise.resolve(signDustRegistration(signatureData));
208
- return await this.dust.addDustGenerationSignature(transaction, signature);
209
276
  }
210
277
  state() {
211
278
  return combineLatest([
@@ -349,15 +416,16 @@ export class WalletFacade {
349
416
  switch (recipe.type) {
350
417
  case 'FINALIZED_TRANSACTION': {
351
418
  const signedBalancingTx = await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment);
419
+ const withDustSig = await this.#signDustRegistrationIfPresent(signedBalancingTx, signSegment);
352
420
  return {
353
421
  type: 'FINALIZED_TRANSACTION',
354
422
  originalTransaction: recipe.originalTransaction,
355
- balancingTransaction: signedBalancingTx,
423
+ balancingTransaction: withDustSig,
356
424
  };
357
425
  }
358
426
  case 'UNBOUND_TRANSACTION': {
359
427
  const signedBalancingTx = recipe.balancingTransaction
360
- ? await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment)
428
+ ? await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment).then((tx) => this.#signDustRegistrationIfPresent(tx, signSegment))
361
429
  : undefined;
362
430
  const signedBaseTx = await this.signUnboundTransaction(recipe.baseTransaction, signSegment);
363
431
  return {
@@ -368,13 +436,23 @@ export class WalletFacade {
368
436
  }
369
437
  case 'UNPROVEN_TRANSACTION': {
370
438
  const signedTx = await this.signUnprovenTransaction(recipe.transaction, signSegment);
439
+ const withDustSig = await this.#signDustRegistrationIfPresent(signedTx, signSegment);
371
440
  return {
372
441
  type: 'UNPROVEN_TRANSACTION',
373
- transaction: signedTx,
442
+ transaction: withDustSig,
374
443
  };
375
444
  }
376
445
  }
377
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
+ }
378
456
  async signUnprovenTransaction(tx, signSegment) {
379
457
  return await this.unshielded.signUnprovenTransaction(tx, signSegment);
380
458
  }
@@ -446,8 +524,23 @@ export class WalletFacade {
446
524
  })), (utxosWithMeta) => dustState.estimateDustGeneration(utxosWithMeta, now), (estimatedUtxos) => dustState.capabilities.coinsAndBalances.splitNightUtxos(estimatedUtxos), (split) => split.guaranteed);
447
525
  const fakeSigningKey = ledger.sampleSigningKey();
448
526
  const fakeVerifyingKey = ledger.signatureVerifyingKey(fakeSigningKey);
449
- const fakeRegistrationRecipe = await this.registerNightUtxosForDustGeneration(nightUtxos, fakeVerifyingKey, (payload) => ledger.signData(fakeSigningKey, payload), dustState.address);
450
- 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();
451
544
  const fee = await this.calculateTransactionFee(finalizedFakeTx);
452
545
  return {
453
546
  fee,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midnight-ntwrk/wallet-sdk-facade",
3
- "version": "4.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.3",
28
- "@midnight-ntwrk/wallet-sdk-address-format": "^3.1.1",
29
- "@midnight-ntwrk/wallet-sdk-capabilities": "^3.3.0",
30
- "@midnight-ntwrk/wallet-sdk-dust-wallet": "^4.0.0",
31
- "@midnight-ntwrk/wallet-sdk-indexer-client": "^1.2.1",
32
- "@midnight-ntwrk/wallet-sdk-shielded": "^3.0.0",
33
- "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "^3.0.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.1.0",
38
38
  "@midnight-ntwrk/wallet-sdk-hd": "^3.0.2",
39
- "@midnight-ntwrk/wallet-sdk-utilities": "1.1.1"
39
+ "@midnight-ntwrk/wallet-sdk-utilities": "^1.2.0"
40
40
  },
41
41
  "scripts": {
42
42
  "typecheck": "tsc -b ./tsconfig.json --noEmit --force",