@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 +5 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +106 -13
- package/package.json +10 -10
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
|
|
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
|
-
})),
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
450
|
-
|
|
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.
|
|
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
|
|
28
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
29
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
30
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
31
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
32
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
33
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
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.
|
|
39
|
+
"@midnight-ntwrk/wallet-sdk-utilities": "^1.2.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"typecheck": "tsc -b ./tsconfig.json --noEmit --force",
|