@midnight-ntwrk/wallet-sdk-facade 1.0.0-beta.10

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.
@@ -0,0 +1,47 @@
1
+ import { Observable } from 'rxjs';
2
+ import { ShieldedWalletState, type ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
3
+ import { type UnshieldedWallet, UnshieldedWalletState } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
4
+ import { type Utxo } from '@midnight-ntwrk/wallet-sdk-unshielded-state';
5
+ import { AnyTransaction, DustWallet, DustWalletState } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
6
+ import { ProvingRecipe } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
7
+ import * as ledger from '@midnight-ntwrk/ledger-v6';
8
+ export interface TokenTransfer {
9
+ type: ledger.RawTokenType;
10
+ receiverAddress: string;
11
+ amount: bigint;
12
+ }
13
+ export type CombinedTokenTransfer = {
14
+ type: 'shielded' | 'unshielded';
15
+ outputs: TokenTransfer[];
16
+ };
17
+ export type CombinedSwapInputs = {
18
+ shielded?: Record<ledger.RawTokenType, bigint>;
19
+ unshielded?: Record<ledger.RawTokenType, bigint>;
20
+ };
21
+ export type CombinedSwapOutputs = CombinedTokenTransfer;
22
+ export type TransactionIdentifier = string;
23
+ export declare class FacadeState {
24
+ readonly shielded: ShieldedWalletState;
25
+ readonly unshielded: UnshieldedWalletState;
26
+ readonly dust: DustWalletState;
27
+ get isSynced(): boolean;
28
+ constructor(shielded: ShieldedWalletState, unshielded: UnshieldedWalletState, dust: DustWalletState);
29
+ }
30
+ export declare class WalletFacade {
31
+ shielded: ShieldedWallet;
32
+ unshielded: UnshieldedWallet;
33
+ dust: DustWallet;
34
+ constructor(shieldedWallet: ShieldedWallet, unshieldedWallet: UnshieldedWallet, dustWallet: DustWallet);
35
+ state(): Observable<FacadeState>;
36
+ submitTransaction(tx: ledger.FinalizedTransaction): Promise<TransactionIdentifier>;
37
+ balanceTransaction(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKeys: ledger.DustSecretKey, tx: ledger.Transaction<ledger.SignatureEnabled, ledger.Proofish, ledger.Bindingish>, ttl: Date): Promise<ProvingRecipe.ProvingRecipe<ledger.FinalizedTransaction>>;
38
+ finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<ledger.FinalizedTransaction>): Promise<ledger.FinalizedTransaction>;
39
+ signTransaction(tx: ledger.UnprovenTransaction, signSegment: (data: Uint8Array) => ledger.Signature): Promise<ledger.UnprovenTransaction>;
40
+ calculateTransactionFee(tx: AnyTransaction): Promise<bigint>;
41
+ transferTransaction(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey, outputs: CombinedTokenTransfer[], ttl: Date): Promise<ProvingRecipe.TransactionToProve>;
42
+ registerNightUtxosForDustGeneration(nightUtxos: readonly Utxo[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => Promise<ledger.Signature> | ledger.Signature, dustReceiverAddress?: string): Promise<ProvingRecipe.TransactionToProve>;
43
+ initSwap(zswapSecretKeys: ledger.ZswapSecretKeys, desiredInputs: CombinedSwapInputs, desiredOutputs: CombinedSwapOutputs[], ttl: Date): Promise<ledger.UnprovenTransaction>;
44
+ deregisterFromDustGeneration(nightUtxos: Utxo[], nightVerifyingKey: ledger.SignatureVerifyingKey, signDustRegistration: (payload: Uint8Array) => Promise<ledger.Signature> | ledger.Signature): Promise<ProvingRecipe.TransactionToProve>;
45
+ start(zswapSecretKeys: ledger.ZswapSecretKeys, dustSecretKey: ledger.DustSecretKey): Promise<void>;
46
+ stop(): Promise<void>;
47
+ }
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 2025 Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { combineLatest, map } from 'rxjs';
14
+ import { ProvingRecipe } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
15
+ export class FacadeState {
16
+ shielded;
17
+ unshielded;
18
+ dust;
19
+ get isSynced() {
20
+ return (this.shielded.state.progress.isStrictlyComplete() &&
21
+ this.dust.state.progress.isStrictlyComplete() &&
22
+ this.unshielded.syncProgress?.synced === true);
23
+ }
24
+ constructor(shielded, unshielded, dust) {
25
+ this.shielded = shielded;
26
+ this.unshielded = unshielded;
27
+ this.dust = dust;
28
+ }
29
+ }
30
+ export class WalletFacade {
31
+ shielded;
32
+ unshielded;
33
+ dust;
34
+ constructor(shieldedWallet, unshieldedWallet, dustWallet) {
35
+ this.shielded = shieldedWallet;
36
+ this.unshielded = unshieldedWallet;
37
+ this.dust = dustWallet;
38
+ }
39
+ state() {
40
+ return combineLatest([this.shielded.state, this.unshielded.state(), this.dust.state]).pipe(map(([shieldedState, unshieldedState, dustState]) => new FacadeState(shieldedState, unshieldedState, dustState)));
41
+ }
42
+ async submitTransaction(tx) {
43
+ await this.shielded.submitTransaction(tx, 'Finalized');
44
+ return tx.identifiers().at(-1);
45
+ }
46
+ async balanceTransaction(zswapSecretKeys, dustSecretKeys, tx, ttl) {
47
+ const unshieldedBalancedTx = await this.unshielded.balanceTransaction(tx);
48
+ const recipe = await this.shielded.balanceTransaction(zswapSecretKeys, unshieldedBalancedTx, []);
49
+ switch (recipe.type) {
50
+ case ProvingRecipe.TRANSACTION_TO_PROVE:
51
+ return await this.dust.addFeePayment(dustSecretKeys, recipe.transaction, new Date(), 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, new Date(), ttl);
55
+ if (balancedTx.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
56
+ throw Error('Unexpected transaction type after adding fee payment.');
57
+ }
58
+ return {
59
+ ...recipe,
60
+ transactionToProve: balancedTx.transaction,
61
+ };
62
+ }
63
+ case ProvingRecipe.NOTHING_TO_PROVE: {
64
+ // @TODO fix casting
65
+ const txToBalance = recipe.transaction;
66
+ return await this.dust.addFeePayment(dustSecretKeys, txToBalance, new Date(), ttl);
67
+ }
68
+ }
69
+ }
70
+ async finalizeTransaction(recipe) {
71
+ return await this.shielded.finalizeTransaction(recipe);
72
+ }
73
+ async signTransaction(tx, signSegment) {
74
+ return await this.unshielded.signTransaction(tx, signSegment);
75
+ }
76
+ async calculateTransactionFee(tx) {
77
+ return await this.dust.calculateFee(tx);
78
+ }
79
+ async transferTransaction(zswapSecretKeys, dustSecretKey, outputs, ttl) {
80
+ const unshieldedOutputs = outputs
81
+ .filter((output) => output.type === 'unshielded')
82
+ .flatMap((output) => output.outputs);
83
+ const shieldedOutputs = outputs.filter((output) => output.type === 'shielded').flatMap((output) => output.outputs);
84
+ if (unshieldedOutputs.length === 0 && shieldedOutputs.length === 0) {
85
+ throw Error('At least one shielded or unshielded output is required.');
86
+ }
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, new Date(), 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, new Date(), 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, new Date(), 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.');
127
+ }
128
+ async registerNightUtxosForDustGeneration(nightUtxos, nightVerifyingKey, signDustRegistration, dustReceiverAddress) {
129
+ if (nightUtxos.length === 0) {
130
+ throw Error('At least one Night UTXO is required.');
131
+ }
132
+ const dustState = await this.dust.waitForSyncedState();
133
+ const receiverAddress = dustReceiverAddress ?? dustState.dustAddress;
134
+ const nextBlock = new Date();
135
+ const ttl = new Date(nextBlock.getTime() + 60 * 60 * 1000);
136
+ const transaction = await this.dust.createDustGenerationTransaction(nextBlock, ttl, nightUtxos.map((utxo) => ({ ...utxo, ctime: new Date(utxo.ctime) })), nightVerifyingKey, receiverAddress);
137
+ const intent = transaction.intents?.get(1);
138
+ if (!intent) {
139
+ throw Error('Dust generation transaction is missing intent segment 1.');
140
+ }
141
+ const signatureData = intent.signatureData(1);
142
+ const signature = await Promise.resolve(signDustRegistration(signatureData));
143
+ const recipe = await this.dust.addDustGenerationSignature(transaction, signature);
144
+ if (recipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
145
+ throw Error('Unexpected recipe type returned when registering Night UTXOs.');
146
+ }
147
+ return recipe;
148
+ }
149
+ async initSwap(zswapSecretKeys, desiredInputs, desiredOutputs, ttl) {
150
+ const { shielded: shieldedInputs, unshielded: unshieldedInputs } = desiredInputs;
151
+ const shieldedOutputs = desiredOutputs
152
+ .filter((output) => output.type === 'shielded')
153
+ .flatMap((output) => output.outputs);
154
+ const unshieldedOutputs = desiredOutputs
155
+ .filter((output) => output.type === 'unshielded')
156
+ .flatMap((output) => output.outputs);
157
+ const hasShieldedPart = (shieldedInputs && Object.keys(shieldedInputs).length > 0) || shieldedOutputs.length > 0;
158
+ const hasUnshieldedPart = (unshieldedInputs && Object.keys(unshieldedInputs).length > 0) || unshieldedOutputs.length > 0;
159
+ if (!hasShieldedPart && !hasUnshieldedPart) {
160
+ throw Error('At least one shielded or unshielded swap is required.');
161
+ }
162
+ const shieldedTxRecipe = hasShieldedPart && shieldedInputs !== undefined
163
+ ? await this.shielded.initSwap(zswapSecretKeys, shieldedInputs, shieldedOutputs)
164
+ : undefined;
165
+ const unshieldedTx = hasUnshieldedPart && unshieldedInputs !== undefined
166
+ ? await this.unshielded.initSwap(unshieldedInputs, unshieldedOutputs, ttl)
167
+ : undefined;
168
+ if (shieldedTxRecipe !== undefined && shieldedTxRecipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
169
+ throw Error('Unexpected transaction type.');
170
+ }
171
+ if (shieldedTxRecipe && unshieldedTx) {
172
+ return shieldedTxRecipe.transaction.merge(unshieldedTx);
173
+ }
174
+ if (shieldedTxRecipe) {
175
+ return shieldedTxRecipe.transaction;
176
+ }
177
+ if (unshieldedTx) {
178
+ return unshieldedTx;
179
+ }
180
+ throw Error('Unexpected transaction state.');
181
+ }
182
+ async deregisterFromDustGeneration(nightUtxos, nightVerifyingKey, signDustRegistration) {
183
+ const nextBlock = new Date();
184
+ const ttl = new Date(nextBlock.getTime() + 60 * 60 * 1000);
185
+ const transaction = await this.dust.createDustGenerationTransaction(nextBlock, ttl, nightUtxos.map((utxo) => ({ ...utxo, ctime: new Date(utxo.ctime) })), nightVerifyingKey, undefined);
186
+ const intent = transaction.intents?.get(1);
187
+ if (!intent) {
188
+ throw Error('Dust generation transaction is missing intent segment 1.');
189
+ }
190
+ const signatureData = intent.signatureData(1);
191
+ const signature = await Promise.resolve(signDustRegistration(signatureData));
192
+ const recipe = await this.dust.addDustGenerationSignature(transaction, signature);
193
+ if (recipe.type !== ProvingRecipe.TRANSACTION_TO_PROVE) {
194
+ throw Error('Unexpected recipe type returned when registering Night UTXOs.');
195
+ }
196
+ return recipe;
197
+ }
198
+ async start(zswapSecretKeys, dustSecretKey) {
199
+ await Promise.all([this.shielded.start(zswapSecretKeys), this.unshielded.start(), this.dust.start(dustSecretKey)]);
200
+ }
201
+ async stop() {
202
+ await Promise.all([this.shielded.stop(), this.unshielded.stop(), this.dust.stop()]);
203
+ }
204
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@midnight-ntwrk/wallet-sdk-facade",
3
+ "version": "1.0.0-beta.10",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "author": "Midnight Foundation",
9
+ "license": "Apache-2.0",
10
+ "publishConfig": {
11
+ "registry": "https://npm.pkg.github.com/"
12
+ },
13
+ "files": [
14
+ "dist/"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/midnight-ntwrk/artifacts.git"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@midnight-ntwrk/ledger-v6": "6.1.0-alpha.5",
28
+ "@midnight-ntwrk/wallet-sdk-abstractions": "1.0.0-beta.9",
29
+ "@midnight-ntwrk/wallet-sdk-address-format": "3.0.0-beta.8",
30
+ "@midnight-ntwrk/wallet-sdk-dust-wallet": "1.0.0-beta.9",
31
+ "@midnight-ntwrk/wallet-sdk-hd": "3.0.0-beta.7",
32
+ "@midnight-ntwrk/wallet-sdk-shielded": "1.0.0-beta.10",
33
+ "@midnight-ntwrk/wallet-sdk-unshielded-wallet": "1.0.0-beta.12",
34
+ "rxjs": "^7.5"
35
+ },
36
+ "devDependencies": {
37
+ "eslint": "^9.37.0",
38
+ "publint": "~0.3.14",
39
+ "rimraf": "^6.0.1",
40
+ "testcontainers": "^11.8.1",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^3.2.4"
43
+ },
44
+ "scripts": {
45
+ "typecheck": "tsc -b ./tsconfig.json --noEmit",
46
+ "test": "vitest run",
47
+ "lint": "eslint --max-warnings 0",
48
+ "format": "prettier --write \"**/*.{ts,js,json,yaml,yml}\"",
49
+ "format:check": "prettier --check \"**/*.{ts,js,json,yaml,yml}\"",
50
+ "dist": "tsc -b ./tsconfig.build.json",
51
+ "dist:publish": "tsc -b ./tsconfig.publish.json",
52
+ "clean": "rimraf --glob dist 'tsconfig.*.tsbuildinfo' && date +%s > .clean-timestamp",
53
+ "publint": "publint --strict"
54
+ }
55
+ }