@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.
- package/dist/index.d.ts +82 -13
- package/dist/index.js +237 -111
- 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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 {
|
|
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
|
|
47
|
-
const
|
|
48
|
-
const
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
60
|
-
|
|
193
|
+
type: 'FINALIZED_TRANSACTION',
|
|
194
|
+
originalTransaction: recipe.originalTransaction,
|
|
195
|
+
balancingTransaction: signedBalancingTx,
|
|
61
196
|
};
|
|
62
197
|
}
|
|
63
|
-
case
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
71
|
-
return await this.
|
|
218
|
+
async signUnprovenTransaction(tx, signSegment) {
|
|
219
|
+
return await this.unshielded.signUnprovenTransaction(tx, signSegment);
|
|
72
220
|
}
|
|
73
|
-
async
|
|
74
|
-
return await this.unshielded.
|
|
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(
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
129
|
-
|
|
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
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
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
|
|
162
|
-
? await this.shielded.initSwap(
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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(
|
|
197
|
-
await Promise.all([
|
|
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.
|
|
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
|
|
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.
|
|
30
|
-
"@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0-beta.
|
|
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.
|
|
33
|
-
"@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0-beta.
|
|
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",
|