@midnight-ntwrk/wallet-sdk-facade 1.0.0-beta.15 → 1.0.0-beta.17

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +82 -13
  2. package/dist/index.js +237 -111
  3. package/package.json +7 -6
package/dist/index.d.ts CHANGED
@@ -1,9 +1,34 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { ShieldedWalletState, type ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
3
3
  import { type UnshieldedWallet, UnshieldedWalletState } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
4
- import { AnyTransaction, DustWallet, DustWalletState } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
5
- import { ProvingRecipe } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { AnyTransaction, DustWallet, DustWalletState, CoinsAndBalances as DustCoinsAndBalances } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
6
5
  import * as ledger from '@midnight-ntwrk/ledger-v7';
6
+ export type UnboundTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proof, ledger.PreBinding>;
7
+ type TokenKind = 'dust' | 'shielded' | 'unshielded';
8
+ type TokenKindsToBalance = 'all' | TokenKind[];
9
+ declare const TokenKindsToBalance: {
10
+ allTokenKinds: string[];
11
+ toFlags: (tokenKinds: TokenKindsToBalance) => {
12
+ shouldBalanceUnshielded: boolean;
13
+ shouldBalanceShielded: boolean;
14
+ shouldBalanceDust: boolean;
15
+ };
16
+ };
17
+ export type FinalizedTransactionRecipe = {
18
+ type: 'FINALIZED_TRANSACTION';
19
+ originalTransaction: ledger.FinalizedTransaction;
20
+ balancingTransaction: ledger.UnprovenTransaction;
21
+ };
22
+ export type UnboundTransactionRecipe = {
23
+ type: 'UNBOUND_TRANSACTION';
24
+ baseTransaction: UnboundTransaction;
25
+ balancingTransaction?: ledger.UnprovenTransaction | undefined;
26
+ };
27
+ export type UnprovenTransactionRecipe = {
28
+ type: 'UNPROVEN_TRANSACTION';
29
+ transaction: ledger.UnprovenTransaction;
30
+ };
31
+ export type BalancingRecipe = FinalizedTransactionRecipe | UnboundTransactionRecipe | UnprovenTransactionRecipe;
7
32
  export interface TokenTransfer {
8
33
  type: ledger.RawTokenType;
9
34
  receiverAddress: string;
@@ -33,20 +58,64 @@ export declare class FacadeState {
33
58
  constructor(shielded: ShieldedWalletState, unshielded: UnshieldedWalletState, dust: DustWalletState);
34
59
  }
35
60
  export declare class WalletFacade {
36
- shielded: ShieldedWallet;
37
- unshielded: UnshieldedWallet;
38
- dust: DustWallet;
61
+ readonly shielded: ShieldedWallet;
62
+ readonly unshielded: UnshieldedWallet;
63
+ readonly dust: DustWallet;
39
64
  constructor(shieldedWallet: ShieldedWallet, unshieldedWallet: UnshieldedWallet, dustWallet: DustWallet);
65
+ private defaultTtl;
66
+ private mergeUnprovenTransactions;
67
+ private createDustActionTransaction;
40
68
  state(): Observable<FacadeState>;
69
+ waitForSyncedState(): Promise<FacadeState>;
41
70
  submitTransaction(tx: ledger.FinalizedTransaction): Promise<TransactionIdentifier>;
42
- balanceTransaction(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKeys: ledger.DustSecretKey, tx: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, ttl: Date): Promise<ProvingRecipe.ProvingRecipe<ledger.FinalizedTransaction>>;
43
- finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<ledger.FinalizedTransaction>): Promise<ledger.FinalizedTransaction>;
44
- signTransaction(tx: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Promise<ledger.UnprovenTransaction>;
71
+ balanceFinalizedTransaction(tx: ledger.FinalizedTransaction, secretKeys: {
72
+ shieldedSecretKeys: ledger.ZswapSecretKeys;
73
+ dustSecretKey: ledger.DustSecretKey;
74
+ }, options: {
75
+ ttl: Date;
76
+ tokenKindsToBalance?: TokenKindsToBalance;
77
+ }): Promise<FinalizedTransactionRecipe>;
78
+ balanceUnboundTransaction(tx: UnboundTransaction, secretKeys: {
79
+ shieldedSecretKeys: ledger.ZswapSecretKeys;
80
+ dustSecretKey: ledger.DustSecretKey;
81
+ }, options: {
82
+ ttl: Date;
83
+ tokenKindsToBalance?: TokenKindsToBalance;
84
+ }): Promise<UnboundTransactionRecipe>;
85
+ balanceUnprovenTransaction(tx: ledger.UnprovenTransaction, secretKeys: {
86
+ shieldedSecretKeys: ledger.ZswapSecretKeys;
87
+ dustSecretKey: ledger.DustSecretKey;
88
+ }, options: {
89
+ ttl: Date;
90
+ tokenKindsToBalance?: TokenKindsToBalance;
91
+ }): Promise<UnprovenTransactionRecipe>;
92
+ finalizeRecipe(recipe: BalancingRecipe): Promise<ledger.FinalizedTransaction>;
93
+ signRecipe(recipe: BalancingRecipe, signSegment: (data: Uint8Array) => ledger.Signature): Promise<BalancingRecipe>;
94
+ signUnprovenTransaction(tx: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Promise<ledger.UnprovenTransaction>;
95
+ signUnboundTransaction(tx: UnboundTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Promise<UnboundTransaction>;
96
+ finalizeTransaction(tx: ledger.UnprovenTransaction): Promise<ledger.FinalizedTransaction>;
45
97
  calculateTransactionFee(tx: AnyTransaction): Promise<bigint>;
46
- transferTransaction(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey, outputs: CombinedTokenTransfer[], ttl: Date): Promise<ProvingRecipe.TransactionToProve>;
47
- registerNightUtxosForDustGeneration(nightUtxos: readonly UtxoWithMeta[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => Promise<ledger.Signature> | ledger.Signature, dustReceiverAddress?: string): Promise<ProvingRecipe.TransactionToProve>;
48
- initSwap(zswapSecretKeys: ledger.ZswapSecretKeys, desiredInputs: CombinedSwapInputs, desiredOutputs: CombinedSwapOutputs[], ttl: Date): Promise<ledger.UnprovenTransaction>;
49
- deregisterFromDustGeneration(nightUtxos: UtxoWithMeta[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => Promise<ledger.Signature> | ledger.Signature): Promise<ProvingRecipe.TransactionToProve>;
50
- start(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey): Promise<void>;
98
+ transferTransaction(outputs: CombinedTokenTransfer[], secretKeys: {
99
+ shieldedSecretKeys: ledger.ZswapSecretKeys;
100
+ dustSecretKey: ledger.DustSecretKey;
101
+ }, options: {
102
+ ttl: Date;
103
+ payFees?: boolean;
104
+ }): Promise<UnprovenTransactionRecipe>;
105
+ estimateRegistration(nightUtxos: readonly UtxoWithMeta[]): Promise<{
106
+ fee: bigint;
107
+ dustGenerationEstimations: ReadonlyArray<DustCoinsAndBalances.UtxoWithFullDustDetails>;
108
+ }>;
109
+ initSwap(desiredInputs: CombinedSwapInputs, desiredOutputs: CombinedSwapOutputs[], secretKeys: {
110
+ shieldedSecretKeys: ledger.ZswapSecretKeys;
111
+ dustSecretKey: ledger.DustSecretKey;
112
+ }, options: {
113
+ ttl: Date;
114
+ payFees?: boolean;
115
+ }): Promise<UnprovenTransactionRecipe>;
116
+ registerNightUtxosForDustGeneration(nightUtxos: readonly UtxoWithMeta[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => ledger.Signature, dustReceiverAddress?: string): Promise<UnprovenTransactionRecipe>;
117
+ deregisterFromDustGeneration(nightUtxos: UtxoWithMeta[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => ledger.Signature): Promise<UnprovenTransactionRecipe>;
118
+ start(shieldedSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey): Promise<void>;
51
119
  stop(): Promise<void>;
52
120
  }
121
+ export {};
package/dist/index.js CHANGED
@@ -11,7 +11,18 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { combineLatest, map } from 'rxjs';
14
- import { ProvingRecipe } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
14
+ import { Array as Arr, pipe } from 'effect';
15
+ import * as ledger from '@midnight-ntwrk/ledger-v7';
16
+ const TokenKindsToBalance = new (class {
17
+ allTokenKinds = ['shielded', 'unshielded', 'dust'];
18
+ toFlags = (tokenKinds) => {
19
+ return pipe(tokenKinds, (kinds) => (kinds === 'all' ? this.allTokenKinds : kinds), (kinds) => ({
20
+ shouldBalanceUnshielded: kinds.includes('unshielded'),
21
+ shouldBalanceShielded: kinds.includes('shielded'),
22
+ shouldBalanceDust: kinds.includes('dust'),
23
+ }));
24
+ };
25
+ })();
15
26
  export class FacadeState {
16
27
  shielded;
17
28
  unshielded;
@@ -27,6 +38,7 @@ export class FacadeState {
27
38
  this.dust = dust;
28
39
  }
29
40
  }
41
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
30
42
  export class WalletFacade {
31
43
  shielded;
32
44
  unshielded;
@@ -36,47 +48,188 @@ export class WalletFacade {
36
48
  this.unshielded = unshieldedWallet;
37
49
  this.dust = dustWallet;
38
50
  }
51
+ defaultTtl() {
52
+ return new Date(Date.now() + DEFAULT_TTL_MS);
53
+ }
54
+ mergeUnprovenTransactions(a, b) {
55
+ if (a && b)
56
+ return a.merge(b);
57
+ return a ?? b;
58
+ }
59
+ async createDustActionTransaction(action, nightUtxos, nightVerifyingKey, signDustRegistration) {
60
+ const ttl = this.defaultTtl();
61
+ const transaction = await this.dust.createDustGenerationTransaction(undefined, ttl, nightUtxos.map(({ utxo, meta }) => ({ ...utxo, ctime: meta.ctime })), nightVerifyingKey, action.type === 'registration' ? action.dustReceiverAddress : undefined);
62
+ const intent = transaction.intents?.get(1);
63
+ if (!intent) {
64
+ throw Error('Dust generation transaction is missing intent segment 1.');
65
+ }
66
+ const signatureData = intent.signatureData(1);
67
+ const signature = await Promise.resolve(signDustRegistration(signatureData));
68
+ return await this.dust.addDustGenerationSignature(transaction, signature);
69
+ }
39
70
  state() {
40
71
  return combineLatest([this.shielded.state, this.unshielded.state, this.dust.state]).pipe(map(([shieldedState, unshieldedState, dustState]) => new FacadeState(shieldedState, unshieldedState, dustState)));
41
72
  }
73
+ async waitForSyncedState() {
74
+ const [shieldedState, unshieldedState, dustState] = await Promise.all([
75
+ this.shielded.waitForSyncedState(),
76
+ this.unshielded.waitForSyncedState(),
77
+ this.dust.waitForSyncedState(),
78
+ ]);
79
+ return new FacadeState(shieldedState, unshieldedState, dustState);
80
+ }
42
81
  async submitTransaction(tx) {
43
82
  await this.shielded.submitTransaction(tx, 'Finalized');
44
83
  return tx.identifiers().at(-1);
45
84
  }
46
- async balanceTransaction(zswapSecretKeys, dustSecretKeys, tx, ttl) {
47
- const unshieldedBalancedTx = await this.unshielded.balanceTransaction(tx);
48
- const recipe = await this.shielded.balanceTransaction(zswapSecretKeys, unshieldedBalancedTx, []);
85
+ async balanceFinalizedTransaction(tx, secretKeys, options) {
86
+ const { shieldedSecretKeys, dustSecretKey } = secretKeys;
87
+ const { ttl, tokenKindsToBalance = 'all' } = options;
88
+ const { shouldBalanceDust, shouldBalanceShielded, shouldBalanceUnshielded } = TokenKindsToBalance.toFlags(tokenKindsToBalance);
89
+ // Step 1: Run unshielded and shielded balancing
90
+ const unshieldedBalancingTx = shouldBalanceUnshielded
91
+ ? await this.unshielded.balanceFinalizedTransaction(tx)
92
+ : undefined;
93
+ const shieldedBalancingTx = shouldBalanceShielded
94
+ ? await this.shielded.balanceTransaction(shieldedSecretKeys, tx)
95
+ : undefined;
96
+ // Step 2: Merge unshielded and shielded balancing
97
+ const mergedBalancingTx = this.mergeUnprovenTransactions(shieldedBalancingTx, unshieldedBalancingTx);
98
+ // Step 3: Conditionally add dust/fee balancing
99
+ const feeBalancingTx = shouldBalanceDust
100
+ ? await this.dust.balanceTransactions(dustSecretKey, mergedBalancingTx ? [tx, mergedBalancingTx] : [tx], ttl)
101
+ : undefined;
102
+ // Step 4: Merge fee balancing and create final recipe
103
+ const balancingTx = this.mergeUnprovenTransactions(mergedBalancingTx, feeBalancingTx);
104
+ if (!balancingTx) {
105
+ throw new Error('No balancing transaction was created. Please check your transaction.');
106
+ }
107
+ return {
108
+ type: 'FINALIZED_TRANSACTION',
109
+ originalTransaction: tx,
110
+ balancingTransaction: balancingTx,
111
+ };
112
+ }
113
+ async balanceUnboundTransaction(tx, secretKeys, options) {
114
+ const { shieldedSecretKeys, dustSecretKey } = secretKeys;
115
+ const { ttl, tokenKindsToBalance = 'all' } = options;
116
+ const { shouldBalanceDust, shouldBalanceShielded, shouldBalanceUnshielded } = TokenKindsToBalance.toFlags(tokenKindsToBalance);
117
+ // Step 1: Run unshielded and shielded balancing
118
+ const shieldedBalancingTx = shouldBalanceShielded
119
+ ? await this.shielded.balanceTransaction(shieldedSecretKeys, tx)
120
+ : undefined;
121
+ // For unbound transactions, unshielded balancing happens in place not with a balancing transaction
122
+ const balancedUnshieldedTx = shouldBalanceUnshielded
123
+ ? await this.unshielded.balanceUnboundTransaction(tx)
124
+ : undefined;
125
+ // Step 2: Unbound unshielded tx are balanced in place, use it as base tx if present
126
+ const baseTx = balancedUnshieldedTx ?? tx;
127
+ // Step 3: Conditionally add dust/fee balancing
128
+ const feeBalancingTransaction = shouldBalanceDust
129
+ ? await this.dust.balanceTransactions(dustSecretKey, shieldedBalancingTx ? [baseTx, shieldedBalancingTx] : [baseTx], ttl)
130
+ : undefined;
131
+ // Step 4: Create the final balancing transaction
132
+ const balancingTransaction = this.mergeUnprovenTransactions(shieldedBalancingTx, feeBalancingTransaction);
133
+ // if there is no balancingTransaction and there was no unshielded tx balancing (in place) throw an error.
134
+ if (!balancingTransaction && !balancedUnshieldedTx) {
135
+ throw new Error('No balancing transaction was created. Please check your transaction.');
136
+ }
137
+ return {
138
+ type: 'UNBOUND_TRANSACTION',
139
+ baseTransaction: baseTx,
140
+ balancingTransaction: balancingTransaction ?? undefined,
141
+ };
142
+ }
143
+ async balanceUnprovenTransaction(tx, secretKeys, options) {
144
+ const { shieldedSecretKeys, dustSecretKey } = secretKeys;
145
+ const { ttl, tokenKindsToBalance = 'all' } = options;
146
+ const { shouldBalanceDust, shouldBalanceShielded, shouldBalanceUnshielded } = TokenKindsToBalance.toFlags(tokenKindsToBalance);
147
+ // Step 1: Run unshielded and shielded balancing
148
+ const shieldedBalancingTx = shouldBalanceShielded
149
+ ? await this.shielded.balanceTransaction(shieldedSecretKeys, tx)
150
+ : undefined;
151
+ // For unproven transactions, unshielded balancing happens in place
152
+ const balancedUnshieldedTx = shouldBalanceUnshielded
153
+ ? await this.unshielded.balanceUnprovenTransaction(tx)
154
+ : undefined;
155
+ // Step 2: Use the balanced unshielded tx if present, otherwise use the original tx
156
+ const baseTx = balancedUnshieldedTx ?? tx;
157
+ // Step 3: Merge shielded balancing into base tx if present
158
+ const mergedTx = this.mergeUnprovenTransactions(baseTx, shieldedBalancingTx);
159
+ // Step 4: Conditionally add dust/fee balancing
160
+ const feeBalancingTx = shouldBalanceDust
161
+ ? await this.dust.balanceTransactions(dustSecretKey, [mergedTx], ttl)
162
+ : undefined;
163
+ // Step 5: Merge fee balancing if present
164
+ const balancedTx = this.mergeUnprovenTransactions(mergedTx, feeBalancingTx);
165
+ return {
166
+ type: 'UNPROVEN_TRANSACTION',
167
+ transaction: balancedTx,
168
+ };
169
+ }
170
+ async finalizeRecipe(recipe) {
171
+ switch (recipe.type) {
172
+ case 'FINALIZED_TRANSACTION': {
173
+ const finalizedBalancing = await this.finalizeTransaction(recipe.balancingTransaction);
174
+ return recipe.originalTransaction.merge(finalizedBalancing);
175
+ }
176
+ case 'UNBOUND_TRANSACTION': {
177
+ const finalizedBalancingTx = recipe.balancingTransaction
178
+ ? await this.finalizeTransaction(recipe.balancingTransaction)
179
+ : undefined;
180
+ const finalizedTransaction = recipe.baseTransaction.bind();
181
+ return finalizedBalancingTx ? finalizedTransaction.merge(finalizedBalancingTx) : finalizedTransaction;
182
+ }
183
+ case 'UNPROVEN_TRANSACTION': {
184
+ return await this.finalizeTransaction(recipe.transaction);
185
+ }
186
+ }
187
+ }
188
+ async signRecipe(recipe, signSegment) {
49
189
  switch (recipe.type) {
50
- case ProvingRecipe.TRANSACTION_TO_PROVE:
51
- return await this.dust.addFeePayment(dustSecretKeys, recipe.transaction, ttl);
52
- case ProvingRecipe.BALANCE_TRANSACTION_TO_PROVE: {
53
- // if the shielded wallet returned a proven transaction, we need to pay fees with the dust wallet
54
- const balancedTx = await this.dust.addFeePayment(dustSecretKeys, recipe.transactionToProve, ttl);
55
- if (balancedTx.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
56
- throw Error('Unexpected transaction type after adding fee payment.');
57
- }
190
+ case 'FINALIZED_TRANSACTION': {
191
+ const signedBalancingTx = await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment);
58
192
  return {
59
- ...recipe,
60
- transactionToProve: balancedTx.transaction,
193
+ type: 'FINALIZED_TRANSACTION',
194
+ originalTransaction: recipe.originalTransaction,
195
+ balancingTransaction: signedBalancingTx,
61
196
  };
62
197
  }
63
- case ProvingRecipe.NOTHING_TO_PROVE: {
64
- // @TODO fix casting
65
- const txToBalance = recipe.transaction;
66
- return await this.dust.addFeePayment(dustSecretKeys, txToBalance, ttl);
198
+ case 'UNBOUND_TRANSACTION': {
199
+ const signedBalancingTx = recipe.balancingTransaction
200
+ ? await this.signUnprovenTransaction(recipe.balancingTransaction, signSegment)
201
+ : undefined;
202
+ const signedBaseTx = await this.signUnboundTransaction(recipe.baseTransaction, signSegment);
203
+ return {
204
+ type: 'UNBOUND_TRANSACTION',
205
+ baseTransaction: signedBaseTx,
206
+ balancingTransaction: signedBalancingTx,
207
+ };
208
+ }
209
+ case 'UNPROVEN_TRANSACTION': {
210
+ const signedTx = await this.signUnprovenTransaction(recipe.transaction, signSegment);
211
+ return {
212
+ type: 'UNPROVEN_TRANSACTION',
213
+ transaction: signedTx,
214
+ };
67
215
  }
68
216
  }
69
217
  }
70
- async finalizeTransaction(recipe) {
71
- return await this.shielded.finalizeTransaction(recipe);
218
+ async signUnprovenTransaction(tx, signSegment) {
219
+ return await this.unshielded.signUnprovenTransaction(tx, signSegment);
72
220
  }
73
- async signTransaction(tx, signSegment) {
74
- return await this.unshielded.signTransaction(tx, signSegment);
221
+ async signUnboundTransaction(tx, signSegment) {
222
+ return await this.unshielded.signUnboundTransaction(tx, signSegment);
223
+ }
224
+ async finalizeTransaction(tx) {
225
+ return await this.shielded.finalizeTransaction(tx);
75
226
  }
76
227
  async calculateTransactionFee(tx) {
77
- return await this.dust.calculateFee(tx);
228
+ return await this.dust.calculateFee([tx]);
78
229
  }
79
- async transferTransaction(zswapSecretKeys, dustSecretKey, outputs, ttl) {
230
+ async transferTransaction(outputs, secretKeys, options) {
231
+ const { shieldedSecretKeys, dustSecretKey } = secretKeys;
232
+ const { ttl, payFees = true } = options;
80
233
  const unshieldedOutputs = outputs
81
234
  .filter((output) => output.type === 'unshielded')
82
235
  .flatMap((output) => output.outputs);
@@ -84,68 +237,36 @@ export class WalletFacade {
84
237
  if (unshieldedOutputs.length === 0 && shieldedOutputs.length === 0) {
85
238
  throw Error('At least one shielded or unshielded output is required.');
86
239
  }
87
- let shieldedTxRecipe = undefined;
88
- let unshieldedTx = undefined;
89
- if (unshieldedOutputs.length > 0) {
90
- unshieldedTx = await this.unshielded.transferTransaction(unshieldedOutputs, ttl);
91
- }
92
- if (shieldedOutputs.length > 0) {
93
- shieldedTxRecipe = await this.shielded.transferTransaction(zswapSecretKeys, shieldedOutputs);
94
- }
95
- // if there's a shielded tx only, return it as it's already balanced
96
- if (shieldedTxRecipe !== undefined && unshieldedTx === undefined) {
97
- if (shieldedTxRecipe.type !== 'TransactionToProve') {
98
- throw Error('Unexpected transaction type.');
99
- }
100
- const recipe = await this.dust.addFeePayment(dustSecretKey, shieldedTxRecipe.transaction, ttl);
101
- if (recipe.type !== 'TransactionToProve') {
102
- throw Error('Unexpected transaction type after adding fee payment.');
103
- }
104
- return recipe;
105
- }
106
- // if there's an unshielded tx only, pay fees (balance) with shielded wallet
107
- if (shieldedTxRecipe === undefined && unshieldedTx !== undefined) {
108
- const recipe = await this.dust.addFeePayment(dustSecretKey, unshieldedTx, ttl);
109
- if (recipe.type !== 'TransactionToProve') {
110
- throw Error('Unexpected transaction type after adding fee payment.');
111
- }
112
- return recipe;
113
- }
114
- // if there's a shielded and unshielded tx, pay fees for unshielded and merge them
115
- if (shieldedTxRecipe !== undefined && unshieldedTx !== undefined) {
116
- if (shieldedTxRecipe.type !== 'TransactionToProve') {
117
- throw Error('Unexpected transaction type.');
118
- }
119
- const txToBalance = shieldedTxRecipe.transaction.merge(unshieldedTx);
120
- const recipe = await this.dust.addFeePayment(dustSecretKey, txToBalance, ttl);
121
- if (recipe.type !== 'TransactionToProve') {
122
- throw Error('Unexpected transaction type after adding fee payment.');
123
- }
124
- return recipe;
125
- }
126
- throw Error('Unexpected transaction state.');
240
+ const shieldedTx = shieldedOutputs.length > 0
241
+ ? await this.shielded.transferTransaction(shieldedSecretKeys, shieldedOutputs)
242
+ : undefined;
243
+ const unshieldedTx = unshieldedOutputs.length > 0 ? await this.unshielded.transferTransaction(unshieldedOutputs, ttl) : undefined;
244
+ const mergedTxs = this.mergeUnprovenTransactions(shieldedTx, unshieldedTx);
245
+ // Add fee payment
246
+ const feeBalancingTx = payFees ? await this.dust.balanceTransactions(dustSecretKey, [mergedTxs], ttl) : undefined;
247
+ const finalTx = this.mergeUnprovenTransactions(mergedTxs, feeBalancingTx);
248
+ return {
249
+ type: 'UNPROVEN_TRANSACTION',
250
+ transaction: finalTx,
251
+ };
127
252
  }
128
- async registerNightUtxosForDustGeneration(nightUtxos, nightVerifyingKey, signDustRegistration, dustReceiverAddress) {
129
- if (nightUtxos.length === 0) {
130
- throw Error('At least one Night UTXO is required.');
131
- }
253
+ async estimateRegistration(nightUtxos) {
254
+ const now = new Date();
132
255
  const dustState = await this.dust.waitForSyncedState();
133
- const receiverAddress = dustReceiverAddress ?? dustState.dustAddress;
134
- const ttl = new Date(Date.now() + 60 * 60 * 1000);
135
- const transaction = await this.dust.createDustGenerationTransaction(undefined, ttl, nightUtxos.map(({ utxo, meta }) => ({ ...utxo, ctime: meta.ctime })), nightVerifyingKey, receiverAddress);
136
- const intent = transaction.intents?.get(1);
137
- if (!intent) {
138
- throw Error('Dust generation transaction is missing intent segment 1.');
139
- }
140
- const signatureData = intent.signatureData(1);
141
- const signature = await Promise.resolve(signDustRegistration(signatureData));
142
- const recipe = await this.dust.addDustGenerationSignature(transaction, signature);
143
- if (recipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
144
- throw Error('Unexpected recipe type returned when registering Night UTXOs.');
145
- }
146
- return recipe;
256
+ const dustGenerationEstimations = pipe(nightUtxos, Arr.map(({ utxo, meta }) => ({ ...utxo, ctime: meta.ctime })), (utxosWithMeta) => dustState.estimateDustGeneration(utxosWithMeta, now), (estimatedUtxos) => dustState.capabilities.coinsAndBalances.splitNightUtxos(estimatedUtxos), (split) => split.guaranteed);
257
+ const fakeSigningKey = ledger.sampleSigningKey();
258
+ const fakeVerifyingKey = ledger.signatureVerifyingKey(fakeSigningKey);
259
+ const fakeRegistrationRecipe = await this.registerNightUtxosForDustGeneration(nightUtxos, fakeVerifyingKey, (payload) => ledger.signData(fakeSigningKey, payload), dustState.dustAddress);
260
+ const finalizedFakeTx = fakeRegistrationRecipe.transaction.mockProve().bind();
261
+ const fee = await this.calculateTransactionFee(finalizedFakeTx);
262
+ return {
263
+ fee,
264
+ dustGenerationEstimations,
265
+ };
147
266
  }
148
- async initSwap(zswapSecretKeys, desiredInputs, desiredOutputs, ttl) {
267
+ async initSwap(desiredInputs, desiredOutputs, secretKeys, options) {
268
+ const { shieldedSecretKeys, dustSecretKey } = secretKeys;
269
+ const { ttl, payFees = false } = options;
149
270
  const { shielded: shieldedInputs, unshielded: unshieldedInputs } = desiredInputs;
150
271
  const shieldedOutputs = desiredOutputs
151
272
  .filter((output) => output.type === 'shielded')
@@ -158,43 +279,48 @@ export class WalletFacade {
158
279
  if (!hasShieldedPart && !hasUnshieldedPart) {
159
280
  throw Error('At least one shielded or unshielded swap is required.');
160
281
  }
161
- const shieldedTxRecipe = hasShieldedPart && shieldedInputs !== undefined
162
- ? await this.shielded.initSwap(zswapSecretKeys, shieldedInputs, shieldedOutputs)
282
+ const shieldedTx = hasShieldedPart && shieldedInputs !== undefined
283
+ ? await this.shielded.initSwap(shieldedSecretKeys, shieldedInputs, shieldedOutputs)
163
284
  : undefined;
164
285
  const unshieldedTx = hasUnshieldedPart && unshieldedInputs !== undefined
165
286
  ? await this.unshielded.initSwap(unshieldedInputs, unshieldedOutputs, ttl)
166
287
  : undefined;
167
- if (shieldedTxRecipe !== undefined && shieldedTxRecipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
168
- throw Error('Unexpected transaction type.');
169
- }
170
- if (shieldedTxRecipe && unshieldedTx) {
171
- return shieldedTxRecipe.transaction.merge(unshieldedTx);
172
- }
173
- if (shieldedTxRecipe) {
174
- return shieldedTxRecipe.transaction;
288
+ const combinedTx = this.mergeUnprovenTransactions(shieldedTx, unshieldedTx);
289
+ if (!combinedTx) {
290
+ throw Error('Unexpected transaction state.');
175
291
  }
176
- if (unshieldedTx) {
177
- return unshieldedTx;
292
+ const feeBalancingTx = payFees ? await this.dust.balanceTransactions(dustSecretKey, [combinedTx], ttl) : undefined;
293
+ const finalTx = this.mergeUnprovenTransactions(combinedTx, feeBalancingTx);
294
+ return {
295
+ type: 'UNPROVEN_TRANSACTION',
296
+ transaction: finalTx,
297
+ };
298
+ }
299
+ async registerNightUtxosForDustGeneration(nightUtxos, nightVerifyingKey, signDustRegistration, dustReceiverAddress) {
300
+ if (nightUtxos.length === 0) {
301
+ throw Error('At least one Night UTXO is required.');
178
302
  }
179
- throw Error('Unexpected transaction state.');
303
+ const dustState = await this.dust.waitForSyncedState();
304
+ const receiverAddress = dustReceiverAddress ?? dustState.dustAddress;
305
+ const dustRegistrationTx = await this.createDustActionTransaction({ type: 'registration', dustReceiverAddress: receiverAddress }, nightUtxos, nightVerifyingKey, signDustRegistration);
306
+ return {
307
+ type: 'UNPROVEN_TRANSACTION',
308
+ transaction: dustRegistrationTx,
309
+ };
180
310
  }
181
311
  async deregisterFromDustGeneration(nightUtxos, nightVerifyingKey, signDustRegistration) {
182
- const ttl = new Date(Date.now() + 60 * 60 * 1000);
183
- const transaction = await this.dust.createDustGenerationTransaction(undefined, ttl, nightUtxos.map(({ utxo, meta }) => ({ ...utxo, ctime: meta.ctime })), nightVerifyingKey, undefined);
184
- const intent = transaction.intents?.get(1);
185
- if (!intent) {
186
- throw Error('Dust generation transaction is missing intent segment 1.');
187
- }
188
- const signatureData = intent.signatureData(1);
189
- const signature = await Promise.resolve(signDustRegistration(signatureData));
190
- const recipe = await this.dust.addDustGenerationSignature(transaction, signature);
191
- if (recipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
192
- throw Error('Unexpected recipe type returned when registering Night UTXOs.');
193
- }
194
- return recipe;
312
+ const dustDeregistrationTx = await this.createDustActionTransaction({ type: 'deregistration' }, nightUtxos, nightVerifyingKey, signDustRegistration);
313
+ return {
314
+ type: 'UNPROVEN_TRANSACTION',
315
+ transaction: dustDeregistrationTx,
316
+ };
195
317
  }
196
- async start(zswapSecretKeys, dustSecretKey) {
197
- await Promise.all([this.shielded.start(zswapSecretKeys), this.unshielded.start(), this.dust.start(dustSecretKey)]);
318
+ async start(shieldedSecretKeys, dustSecretKey) {
319
+ await Promise.all([
320
+ this.shielded.start(shieldedSecretKeys),
321
+ this.unshielded.start(),
322
+ this.dust.start(dustSecretKey),
323
+ ]);
198
324
  }
199
325
  async stop() {
200
326
  await Promise.all([this.shielded.stop(), this.unshielded.stop(), this.dust.stop()]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midnight-ntwrk/wallet-sdk-facade",
3
- "version": "1.0.0-beta.15",
3
+ "version": "1.0.0-beta.17",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -24,16 +24,17 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@midnight-ntwrk/ledger-v7": "7.0.0-rc.1",
27
+ "@midnight-ntwrk/ledger-v7": "7.0.0",
28
28
  "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0-beta.10",
29
- "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.11",
30
- "@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0-beta.14",
29
+ "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.12",
30
+ "@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0-beta.16",
31
31
  "@midnight-ntwrk/wallet-sdk-hd": "3.0.0-beta.8",
32
- "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0-beta.15",
33
- "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0-beta.17",
32
+ "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0-beta.17",
33
+ "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0-beta.19",
34
34
  "rxjs": "^7.5"
35
35
  },
36
36
  "devDependencies": {
37
+ "@midnight-ntwrk/wallet-sdk-prover-client": "1.0.0-beta.14",
37
38
  "eslint": "^9.37.0",
38
39
  "prettier": "^3.7.0",
39
40
  "publint": "~0.3.14",