@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 +8 -0
- package/dist/index.d.ts +129 -18
- package/dist/index.js +166 -29
- package/dist/transaction.d.ts +1 -1
- package/package.json +11 -11
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
|
-
*
|
|
78
|
-
*
|
|
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
|
-
*
|
|
89
|
-
*
|
|
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
|
-
*
|
|
118
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
126
|
-
* @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if
|
|
127
|
-
*
|
|
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),
|
|
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
|
-
*
|
|
98
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
106
|
-
* @returns A promise resolving to the current {@link TermsAndConditions}, or rejecting if
|
|
107
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
})),
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
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:
|
|
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:
|
|
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),
|
|
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 =
|
|
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
|
-
|
|
414
|
-
|
|
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
|
}
|
package/dist/transaction.d.ts
CHANGED
|
@@ -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
|
+
"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-
|
|
38
|
-
"@midnight-ntwrk/wallet-sdk-
|
|
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",
|