@midnightntwrk/wallet-sdk-shielded 3.0.2

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 ADDED
@@ -0,0 +1,124 @@
1
+ # @midnightntwrk/wallet-sdk-shielded
2
+
3
+ Manages shielded tokens on the Midnight network using zero-knowledge proofs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @midnightntwrk/wallet-sdk-shielded
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ The Shielded Wallet handles private token operations where transaction values and addresses are hidden from observers
14
+ while maintaining verifiability. It provides:
15
+
16
+ - Zero-knowledge proof generation
17
+ - Coin commitment tracking
18
+ - Encrypted output decryption
19
+ - Shielded transfer transactions
20
+ - Transaction balancing for shielded tokens
21
+
22
+ ## Usage
23
+
24
+ ### Starting the Wallet
25
+
26
+ ```typescript
27
+ import { ShieldedWallet } from '@midnightntwrk/wallet-sdk-shielded';
28
+ import * as ledger from '@midnight-ntwrk/ledger-v7';
29
+ import { randomBytes } from 'node:crypto';
30
+
31
+ // Configuration for the wallet
32
+ const configuration = {
33
+ networkId: 'preview',
34
+ provingServerUrl: new URL('http://localhost:6300'),
35
+ relayURL: new URL('ws://localhost:9944'),
36
+ indexerClientConnection: {
37
+ indexerHttpUrl: 'http://localhost:8088/api/v3/graphql',
38
+ indexerWsUrl: 'ws://localhost:8088/api/v3/graphql/ws',
39
+ },
40
+ };
41
+
42
+ // Create secret keys from a shielded seed
43
+ const seed = randomBytes(32);
44
+ const shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(seed);
45
+
46
+ // Create and start the wallet
47
+ const shieldedWallet = ShieldedWallet(configuration).startWithSecretKeys(shieldedSecretKeys);
48
+
49
+ // Start syncing with the network
50
+ await shieldedWallet.start(shieldedSecretKeys);
51
+ ```
52
+
53
+ ### Alternative: Start with Seed Directly
54
+
55
+ ```typescript
56
+ // Start directly with a shielded seed
57
+ const shieldedWallet = ShieldedWallet(configuration).startWithSeed(seed);
58
+ await shieldedWallet.start(ledger.ZswapSecretKeys.fromSeed(seed));
59
+ ```
60
+
61
+ ### Restoring from Serialized State
62
+
63
+ ```typescript
64
+ // Restore a wallet from previously serialized state
65
+ const shieldedWallet = ShieldedWallet(configuration).restore(serializedState);
66
+ await shieldedWallet.start(shieldedSecretKeys);
67
+ ```
68
+
69
+ ### Observing State
70
+
71
+ ```typescript
72
+ wallet.state.subscribe((state) => {
73
+ console.log('Progress:', state.state.progress);
74
+ console.log('Coins:', state.state.coins);
75
+ });
76
+ ```
77
+
78
+ ### Creating Transfer Transactions
79
+
80
+ ```typescript
81
+ const tx = await wallet.transferTransaction(shieldedSecretKeys, [
82
+ { type: 'NIGHT', receiverAddress: 'mn_shield-addr1...', amount: 1000n },
83
+ ]);
84
+ ```
85
+
86
+ ### Balancing Transactions
87
+
88
+ ```typescript
89
+ const balancingTx = await wallet.balanceTransaction(shieldedSecretKeys, transactionToBalance);
90
+ ```
91
+
92
+ ### Creating Swap Offers
93
+
94
+ ```typescript
95
+ const swapTx = await wallet.initSwap(
96
+ shieldedSecretKeys,
97
+ { NIGHT: 500n }, // inputs
98
+ [{ type: 'TOKEN_A', receiverAddress: shieldedAddress, amount: 100n }], // outputs
99
+ );
100
+ ```
101
+
102
+ ## Privacy Model
103
+
104
+ Shielded transactions use zero-knowledge proofs to hide:
105
+
106
+ - Transaction amounts
107
+ - Sender addresses
108
+ - Receiver addresses
109
+
110
+ While still proving:
111
+
112
+ - The sender has sufficient balance
113
+ - No tokens are created or destroyed
114
+ - The transaction is valid
115
+
116
+ ## Exports
117
+
118
+ - `ShieldedWallet` - Main wallet class
119
+ - `ShieldedWalletState` - Wallet state type
120
+ - Version 1 exports via `@midnightntwrk/wallet-sdk-shielded/v1`
121
+
122
+ ## License
123
+
124
+ Apache-2.0
@@ -0,0 +1,65 @@
1
+ import { type ProtocolState, ProtocolVersion, type SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
2
+ import { type BaseV1Configuration, type DefaultV1Configuration, type V1Variant, CoreWallet } from './v1/index.js';
3
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
4
+ import * as rx from 'rxjs';
5
+ import { type BalancingResult } from './v1/Transacting.js';
6
+ import { type SerializationCapability } from './v1/Serialization.js';
7
+ import { type TransactionHistoryService } from './v1/TransactionHistory.js';
8
+ import { type AvailableCoin, type CoinsAndBalancesCapability, type PendingCoin } from './v1/CoinsAndBalances.js';
9
+ import { type KeysCapability } from './v1/Keys.js';
10
+ import { type ShieldedAddress, type ShieldedCoinPublicKey, type ShieldedEncryptionPublicKey } from '@midnightntwrk/wallet-sdk-address-format';
11
+ import { type TokenTransfer } from './v1/Transacting.js';
12
+ import { type WalletSyncUpdate } from './v1/Sync.js';
13
+ import { type Variant, type VariantBuilder, type WalletLike } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
14
+ export type ShieldedWalletCapabilities<TSerialized = string> = {
15
+ serialization: SerializationCapability<CoreWallet, null, TSerialized>;
16
+ coinsAndBalances: CoinsAndBalancesCapability<CoreWallet>;
17
+ keys: KeysCapability<CoreWallet>;
18
+ };
19
+ export type ShieldedWalletServices = {
20
+ transactionHistory: TransactionHistoryService;
21
+ };
22
+ export type UnboundTransaction = ledger.Transaction<ledger.SignatureEnabled, ledger.Proof, ledger.PreBinding>;
23
+ export declare class ShieldedWalletState<TSerialized = string, _TTransaction = ledger.FinalizedTransaction> {
24
+ static readonly mapState: <TSerialized_1 = string>(variant: ShieldedWalletCapabilities<TSerialized_1> & ShieldedWalletServices) => (state: ProtocolState.ProtocolState<CoreWallet>) => ShieldedWalletState<TSerialized_1>;
25
+ readonly protocolVersion: ProtocolVersion.ProtocolVersion;
26
+ readonly state: CoreWallet;
27
+ readonly capabilities: ShieldedWalletCapabilities<TSerialized>;
28
+ readonly services: ShieldedWalletServices;
29
+ get balances(): Record<ledger.RawTokenType, bigint>;
30
+ get totalCoins(): readonly (AvailableCoin | PendingCoin)[];
31
+ get availableCoins(): readonly AvailableCoin[];
32
+ get pendingCoins(): readonly PendingCoin[];
33
+ get coinPublicKey(): ShieldedCoinPublicKey;
34
+ get encryptionPublicKey(): ShieldedEncryptionPublicKey;
35
+ get address(): ShieldedAddress;
36
+ get progress(): SyncProgress.SyncProgress;
37
+ constructor(state: ProtocolState.ProtocolState<CoreWallet>, capabilities: ShieldedWalletCapabilities<TSerialized>, services: ShieldedWalletServices);
38
+ serialize(): TSerialized;
39
+ }
40
+ export type ShieldedWallet = CustomizedShieldedWallet<ledger.ZswapSecretKeys, ledger.FinalizedTransaction, WalletSyncUpdate, string>;
41
+ export type ShieldedWalletClass = CustomizedShieldedWalletClass<ledger.ZswapSecretKeys, ledger.FinalizedTransaction, WalletSyncUpdate, string>;
42
+ export type ShieldedWalletAPI<TStartAux = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSerialized = string> = {
43
+ readonly state: rx.Observable<ShieldedWalletState<TSerialized, TTransaction>>;
44
+ start(secretKeys: TStartAux): Promise<void>;
45
+ balanceTransaction(secretKeys: ledger.ZswapSecretKeys, tx: ledger.Transaction<ledger.Signaturish, ledger.Proofish, ledger.Bindingish>): Promise<BalancingResult>;
46
+ transferTransaction(secretKeys: ledger.ZswapSecretKeys, outputs: readonly TokenTransfer[]): Promise<ledger.UnprovenTransaction>;
47
+ initSwap(secretKeys: ledger.ZswapSecretKeys, desiredInputs: Record<ledger.RawTokenType, bigint>, desiredOutputs: readonly TokenTransfer[]): Promise<ledger.UnprovenTransaction>;
48
+ serializeState(): Promise<TSerialized>;
49
+ waitForSyncedState(allowedGap?: bigint): Promise<ShieldedWalletState<TSerialized, TTransaction>>;
50
+ getAddress(): Promise<ShieldedAddress>;
51
+ revertTransaction(transaction: ledger.Transaction<ledger.Signaturish, ledger.Proofish, ledger.Bindingish>): Promise<void>;
52
+ stop(): Promise<void>;
53
+ };
54
+ export type CustomizedShieldedWallet<TStartAux = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string> = ShieldedWalletAPI<TStartAux, TTransaction, TSerialized> & WalletLike.WalletLike<[Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>]>;
55
+ export type DefaultShieldedConfiguration = DefaultV1Configuration;
56
+ export interface CustomizedShieldedWalletClass<TStartAux = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV1Configuration = DefaultShieldedConfiguration> extends WalletLike.BaseWalletClass<[
57
+ Variant.VersionedVariant<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>
58
+ ]> {
59
+ configuration: TConfig;
60
+ startWithSeed(seed: Uint8Array): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
61
+ startWithSecretKeys(secretKeys: ledger.ZswapSecretKeys): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
62
+ restore(serializedState: TSerialized): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
63
+ }
64
+ export declare function ShieldedWallet(configuration: DefaultV1Configuration): ShieldedWalletClass;
65
+ export declare function CustomShieldedWallet<TConfig extends BaseV1Configuration = DefaultV1Configuration, TStartAux = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string>(configuration: TConfig, builder: VariantBuilder.VariantBuilder<V1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>, TConfig>): CustomizedShieldedWalletClass<TStartAux, TTransaction, TSyncUpdate, TSerialized, TConfig>;
@@ -0,0 +1,132 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 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 { ProtocolVersion } from '@midnightntwrk/wallet-sdk-abstractions';
14
+ import { V1Builder, V1Tag, CoreWallet, } from './v1/index.js';
15
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
16
+ import { Effect, Either } from 'effect';
17
+ import * as rx from 'rxjs';
18
+ import { WalletBuilder } from '@midnightntwrk/wallet-sdk-runtime';
19
+ export class ShieldedWalletState {
20
+ static mapState = (variant) => (state) => {
21
+ const { serialization, coinsAndBalances, keys } = variant;
22
+ const { transactionHistory } = variant;
23
+ return new ShieldedWalletState(state, { serialization, coinsAndBalances, keys }, { transactionHistory });
24
+ };
25
+ protocolVersion;
26
+ state;
27
+ capabilities;
28
+ services;
29
+ get balances() {
30
+ return this.capabilities.coinsAndBalances.getAvailableBalances(this.state);
31
+ }
32
+ get totalCoins() {
33
+ return this.capabilities.coinsAndBalances.getTotalCoins(this.state);
34
+ }
35
+ get availableCoins() {
36
+ return this.capabilities.coinsAndBalances.getAvailableCoins(this.state);
37
+ }
38
+ get pendingCoins() {
39
+ return this.capabilities.coinsAndBalances.getPendingCoins(this.state);
40
+ }
41
+ get coinPublicKey() {
42
+ return this.capabilities.keys.getCoinPublicKey(this.state);
43
+ }
44
+ get encryptionPublicKey() {
45
+ return this.capabilities.keys.getEncryptionPublicKey(this.state);
46
+ }
47
+ get address() {
48
+ return this.capabilities.keys.getAddress(this.state);
49
+ }
50
+ get progress() {
51
+ return this.state.progress;
52
+ }
53
+ constructor(state, capabilities, services) {
54
+ this.protocolVersion = state.version;
55
+ this.state = state.state;
56
+ this.capabilities = capabilities;
57
+ this.services = services;
58
+ }
59
+ serialize() {
60
+ return this.capabilities.serialization.serialize(this.state);
61
+ }
62
+ }
63
+ export function ShieldedWallet(configuration) {
64
+ return CustomShieldedWallet(configuration, new V1Builder().withDefaults());
65
+ }
66
+ export function CustomShieldedWallet(configuration, builder) {
67
+ const buildArgs = [configuration];
68
+ const BaseWallet = WalletBuilder.init()
69
+ .withVariant(ProtocolVersion.MinSupportedVersion, builder)
70
+ .build(...buildArgs);
71
+ return class CustomShieldedWalletImplementation extends BaseWallet {
72
+ static startWithSecretKeys(secretKeys) {
73
+ return CustomShieldedWalletImplementation.startFirst(CustomShieldedWalletImplementation, CoreWallet.initEmpty(secretKeys, CustomShieldedWalletImplementation.configuration.networkId));
74
+ }
75
+ static startWithSeed(seed) {
76
+ const secretKeys = ledger.ZswapSecretKeys.fromSeed(seed);
77
+ return CustomShieldedWalletImplementation.startWithSecretKeys(secretKeys);
78
+ }
79
+ static restore(serializedState) {
80
+ const deserialized = CustomShieldedWalletImplementation.allVariantsRecord()[V1Tag].variant.deserializeState(serializedState)
81
+ .pipe(Either.getOrThrow);
82
+ return CustomShieldedWalletImplementation.startFirst(CustomShieldedWalletImplementation, deserialized);
83
+ }
84
+ state;
85
+ constructor(runtime, scope) {
86
+ super(runtime, scope);
87
+ this.state = this.rawState.pipe(rx.map(ShieldedWalletState.mapState(CustomShieldedWalletImplementation.allVariantsRecord()[V1Tag].variant)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
88
+ }
89
+ start(secretKeys) {
90
+ return this.runtime.dispatch({ [V1Tag]: (v1) => v1.startSyncInBackground(secretKeys) }).pipe(Effect.runPromise);
91
+ }
92
+ balanceTransaction(secretKeys, tx) {
93
+ return this.runtime
94
+ .dispatch({
95
+ [V1Tag]: (v1) => v1.balanceTransaction(secretKeys, tx),
96
+ })
97
+ .pipe(Effect.runPromise);
98
+ }
99
+ transferTransaction(secretKeys, outputs) {
100
+ return this.runtime
101
+ .dispatch({
102
+ [V1Tag]: (v1) => v1.transferTransaction(secretKeys, outputs),
103
+ })
104
+ .pipe(Effect.runPromise);
105
+ }
106
+ initSwap(secretKeys, desiredInputs, desiredOutputs) {
107
+ return this.runtime
108
+ .dispatch({ [V1Tag]: (v1) => v1.initSwap(secretKeys, desiredInputs, desiredOutputs) })
109
+ .pipe(Effect.runPromise);
110
+ }
111
+ revertTransaction(transaction) {
112
+ return this.runtime
113
+ .dispatch({
114
+ [V1Tag]: (v1) => v1.revertTransaction(transaction),
115
+ })
116
+ .pipe(Effect.runPromise);
117
+ }
118
+ waitForSyncedState(allowedGap = 0n) {
119
+ return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
120
+ }
121
+ /**
122
+ * Serializes the most recent state It's preferable to use [[ShieldedWalletState.serialize]] instead, to know
123
+ * exactly, which state is serialized
124
+ */
125
+ serializeState() {
126
+ return rx.firstValueFrom(this.state).then((state) => state.serialize());
127
+ }
128
+ getAddress() {
129
+ return rx.firstValueFrom(this.state).then((state) => state.address);
130
+ }
131
+ };
132
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ShieldedWallet.js';
2
+ export { type ShieldedTransactionHistoryEntry, ShieldedSectionSchema, mergeShieldedSections, } from './v1/TransactionHistory.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 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
+ export * from './ShieldedWallet.js';
14
+ export { ShieldedSectionSchema, mergeShieldedSections, } from './v1/TransactionHistory.js';
@@ -0,0 +1,24 @@
1
+ import { type CoreWallet } from './CoreWallet.js';
2
+ import type * as ledger from '@midnight-ntwrk/ledger-v8';
3
+ export type AvailableCoin = {
4
+ coin: ledger.QualifiedShieldedCoinInfo;
5
+ commitment: ledger.CoinCommitment;
6
+ nullifier: ledger.Nullifier;
7
+ };
8
+ export type PendingCoin = {
9
+ coin: ledger.ShieldedCoinInfo;
10
+ ttl: Date | undefined;
11
+ commitment: ledger.CoinCommitment;
12
+ nullifier: ledger.Nullifier;
13
+ };
14
+ export type Coin = AvailableCoin | PendingCoin;
15
+ export type Balances = Record<ledger.RawTokenType, bigint>;
16
+ export type CoinsAndBalancesCapability<TState> = {
17
+ getAvailableBalances(state: TState): Balances;
18
+ getPendingBalances(state: TState): Balances;
19
+ getTotalBalances(state: TState): Balances;
20
+ getAvailableCoins(state: TState): readonly AvailableCoin[];
21
+ getPendingCoins(state: TState): readonly PendingCoin[];
22
+ getTotalCoins(state: TState): ReadonlyArray<AvailableCoin | PendingCoin>;
23
+ };
24
+ export declare const makeDefaultCoinsAndBalancesCapability: () => CoinsAndBalancesCapability<CoreWallet>;
@@ -0,0 +1,46 @@
1
+ import { pipe, Array } from 'effect';
2
+ import { RecordOps } from '@midnightntwrk/wallet-sdk-utilities';
3
+ const calculateBalances = (coins) => coins.reduce((acc, { coin }) => ({
4
+ ...acc,
5
+ [coin.type]: acc[coin.type] === undefined ? coin.value : acc[coin.type] + coin.value,
6
+ }), {});
7
+ export const makeDefaultCoinsAndBalancesCapability = () => {
8
+ const getAvailableBalances = (state) => {
9
+ const availableCoins = getAvailableCoins(state);
10
+ return calculateBalances(availableCoins);
11
+ };
12
+ const getPendingBalances = (state) => {
13
+ const pendingCoins = getPendingCoins(state);
14
+ return calculateBalances(pendingCoins);
15
+ };
16
+ const getTotalBalances = (state) => {
17
+ const availableBalances = getAvailableBalances(state);
18
+ const pendingBalances = getPendingBalances(state);
19
+ return pipe([availableBalances, pendingBalances], RecordOps.mergeWithAccumulator(0n, (a, b) => a + b));
20
+ };
21
+ const getAvailableCoins = (state) => {
22
+ const pendingSpends = new Set([...state.state.pendingSpends.values()].map(([coin]) => coin.nonce));
23
+ return pipe([...state.state.coins], Array.filter((coin) => !pendingSpends.has(coin.nonce)), Array.map((coin) => {
24
+ return {
25
+ coin,
26
+ commitment: state.coinHashes[coin.nonce].commitment,
27
+ nullifier: state.coinHashes[coin.nonce].nullifier,
28
+ };
29
+ }));
30
+ };
31
+ const getPendingCoins = (state) => pipe([...state.state.pendingOutputs.values()], Array.map(([coin, ttl]) => ({
32
+ coin,
33
+ ttl,
34
+ commitment: state.coinHashes[coin.nonce].commitment,
35
+ nullifier: state.coinHashes[coin.nonce].nullifier,
36
+ })));
37
+ const getTotalCoins = (state) => [...getAvailableCoins(state), ...getPendingCoins(state)];
38
+ return {
39
+ getAvailableBalances,
40
+ getPendingBalances,
41
+ getTotalBalances,
42
+ getAvailableCoins,
43
+ getPendingCoins,
44
+ getTotalCoins,
45
+ };
46
+ };
@@ -0,0 +1,47 @@
1
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
2
+ import { ProtocolVersion, SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
3
+ import { Either } from 'effect';
4
+ import { type WalletError } from './WalletError.js';
5
+ export type PublicKeys = {
6
+ coinPublicKey: ledger.CoinPublicKey;
7
+ encryptionPublicKey: ledger.EncPublicKey;
8
+ };
9
+ export declare const PublicKeys: {
10
+ fromSecretKeys: (secretKeys: ledger.ZswapSecretKeys) => PublicKeys;
11
+ };
12
+ export type CoinHashesMap = Readonly<Record<ledger.Nonce, {
13
+ nullifier: ledger.Nullifier;
14
+ commitment: ledger.CoinCommitment;
15
+ }>>;
16
+ export declare const CoinHashesMap: {
17
+ empty: {};
18
+ pickAllCoins(state: ledger.ZswapLocalState): readonly ledger.ShieldedCoinInfo[];
19
+ assertValid(map: CoinHashesMap, state: ledger.ZswapLocalState): Either.Either<void, Set<ledger.Nonce>>;
20
+ updateWithCoins(secretKeys: ledger.ZswapSecretKeys, existing: CoinHashesMap, coins: Iterable<ledger.ShieldedCoinInfo>): CoinHashesMap;
21
+ updateWithNewCoins(secretKeys: ledger.ZswapSecretKeys, existing: CoinHashesMap, coins: Iterable<ledger.ShieldedCoinInfo>): CoinHashesMap;
22
+ init(secretKeys: ledger.ZswapSecretKeys, coins: Iterable<ledger.ShieldedCoinInfo>): CoinHashesMap;
23
+ };
24
+ export type CoreWallet = Readonly<{
25
+ state: ledger.ZswapLocalState;
26
+ publicKeys: PublicKeys;
27
+ protocolVersion: ProtocolVersion.ProtocolVersion;
28
+ progress: SyncProgress.SyncProgress;
29
+ networkId: string;
30
+ coinHashes: CoinHashesMap;
31
+ }>;
32
+ export declare const CoreWallet: {
33
+ init(localState: ledger.ZswapLocalState, secretKeys: ledger.ZswapSecretKeys, networkId: string): CoreWallet;
34
+ empty(publicKeys: PublicKeys, networkId: string): CoreWallet;
35
+ restore(localState: ledger.ZswapLocalState, secretKeys: ledger.ZswapSecretKeys, syncProgress: Omit<SyncProgress.SyncProgressData, "isConnected">, protocolVersion: bigint, networkId: string): CoreWallet;
36
+ restoreWithCoinHashes(publicKeys: PublicKeys, localState: ledger.ZswapLocalState, coinHashes: CoinHashesMap, syncProgress: SyncProgress.SyncProgressData, protocolVersion: bigint, networkId: string): Either.Either<CoreWallet, WalletError>;
37
+ initEmpty(keys: ledger.ZswapSecretKeys, networkId: string): CoreWallet;
38
+ applyCollapsedUpdate(wallet: CoreWallet, collapsed: ledger.MerkleTreeCollapsedUpdate): CoreWallet;
39
+ apply<TOffer extends ledger.ZswapOffer<ledger.Proofish>>(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, offer: TOffer): CoreWallet;
40
+ replayEventsWithChanges(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, events: ledger.Event[]): [CoreWallet, ledger.ZswapStateChanges[]];
41
+ updateProgress(wallet: CoreWallet, { appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }: Partial<SyncProgress.SyncProgressData>): CoreWallet;
42
+ addTransaction(wallet: CoreWallet, _tx: ledger.FinalizedTransaction): CoreWallet;
43
+ revertTransaction<TTx extends ledger.Transaction<ledger.Signaturish, ledger.Proofish, ledger.Bindingish>>(wallet: CoreWallet, tx: TTx): CoreWallet;
44
+ updateTxHistory(wallet: CoreWallet, _newTxs: readonly ledger.FinalizedTransaction[]): CoreWallet;
45
+ spendCoins(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, coins: ReadonlyArray<ledger.QualifiedShieldedCoinInfo>, segment: number): [ReadonlyArray<ledger.ZswapOffer<ledger.PreProof>>, CoreWallet];
46
+ watchCoins(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, coins: ReadonlyArray<ledger.ShieldedCoinInfo>): CoreWallet;
47
+ };
@@ -0,0 +1,153 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 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 * as ledger from '@midnight-ntwrk/ledger-v8';
14
+ import { ProtocolVersion, SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
15
+ import { Either, Iterable, pipe, Record, Array as Arr } from 'effect';
16
+ import { InvalidCoinHashesError } from './WalletError.js';
17
+ export const PublicKeys = {
18
+ fromSecretKeys: (secretKeys) => {
19
+ return {
20
+ coinPublicKey: secretKeys.coinPublicKey,
21
+ encryptionPublicKey: secretKeys.encryptionPublicKey,
22
+ };
23
+ },
24
+ };
25
+ export const CoinHashesMap = {
26
+ empty: {},
27
+ pickAllCoins(state) {
28
+ return [...state.coins, ...state.pendingOutputs.values().map(([coin]) => coin)];
29
+ },
30
+ assertValid(map, state) {
31
+ const coins = CoinHashesMap.pickAllCoins(state);
32
+ const coinNonces = new Set(Iterable.map(coins, (coin) => coin.nonce));
33
+ const definedNonces = new Set(Object.keys(map));
34
+ const missingNonces = coinNonces.difference(definedNonces);
35
+ return missingNonces.size === 0 ? Either.void : Either.left(missingNonces);
36
+ },
37
+ updateWithCoins(secretKeys, existing, coins) {
38
+ return Record.fromIterableWith(coins, (coin) => [
39
+ coin.nonce,
40
+ existing[coin.nonce] ?? {
41
+ commitment: ledger.coinCommitment(coin, secretKeys.coinPublicKey),
42
+ nullifier: ledger.coinNullifier(coin, secretKeys.coinSecretKey),
43
+ },
44
+ ]);
45
+ },
46
+ updateWithNewCoins(secretKeys, existing, coins) {
47
+ const newMap = CoinHashesMap.updateWithCoins(secretKeys, CoinHashesMap.empty, coins);
48
+ return Record.union(existing, newMap, (a) => a);
49
+ },
50
+ init(secretKeys, coins) {
51
+ return CoinHashesMap.updateWithCoins(secretKeys, {}, coins);
52
+ },
53
+ };
54
+ export const CoreWallet = {
55
+ init(localState, secretKeys, networkId) {
56
+ const publicKeys = PublicKeys.fromSecretKeys(secretKeys);
57
+ const coinHashes = CoinHashesMap.init(secretKeys, CoinHashesMap.pickAllCoins(localState));
58
+ const progress = SyncProgress.createSyncProgress();
59
+ const protocolVersion = ProtocolVersion.MinSupportedVersion;
60
+ return { state: localState, publicKeys, networkId, coinHashes, progress, protocolVersion };
61
+ },
62
+ empty(publicKeys, networkId) {
63
+ return {
64
+ state: new ledger.ZswapLocalState(),
65
+ publicKeys,
66
+ networkId,
67
+ coinHashes: CoinHashesMap.empty,
68
+ progress: SyncProgress.createSyncProgress(),
69
+ protocolVersion: ProtocolVersion.MinSupportedVersion,
70
+ };
71
+ },
72
+ restore(localState, secretKeys, syncProgress, protocolVersion, networkId) {
73
+ const publicKeys = PublicKeys.fromSecretKeys(secretKeys);
74
+ const coinHashes = CoinHashesMap.init(secretKeys, CoinHashesMap.pickAllCoins(localState));
75
+ return {
76
+ state: localState,
77
+ publicKeys,
78
+ networkId,
79
+ coinHashes,
80
+ progress: SyncProgress.createSyncProgress(syncProgress),
81
+ protocolVersion: ProtocolVersion.ProtocolVersion(protocolVersion),
82
+ };
83
+ },
84
+ restoreWithCoinHashes(publicKeys, localState, coinHashes, syncProgress, protocolVersion, networkId) {
85
+ return CoinHashesMap.assertValid(coinHashes, localState).pipe(Either.mapBoth({
86
+ onLeft: (missingNonces) => new InvalidCoinHashesError({ message: 'Missing coin hashes for coins present in the state', missingNonces }),
87
+ onRight: () => ({
88
+ state: localState,
89
+ publicKeys,
90
+ networkId,
91
+ coinHashes,
92
+ progress: SyncProgress.createSyncProgress(syncProgress),
93
+ protocolVersion: ProtocolVersion.ProtocolVersion(protocolVersion),
94
+ }),
95
+ }));
96
+ },
97
+ initEmpty(keys, networkId) {
98
+ return this.empty(PublicKeys.fromSecretKeys(keys), networkId);
99
+ },
100
+ applyCollapsedUpdate(wallet, collapsed) {
101
+ const newState = wallet.state.applyCollapsedUpdate(collapsed);
102
+ return { ...wallet, state: newState };
103
+ },
104
+ apply(wallet, secretKeys, offer) {
105
+ const newState = wallet.state.apply(secretKeys, offer);
106
+ const newCoinHashes = CoinHashesMap.updateWithCoins(secretKeys, wallet.coinHashes, CoinHashesMap.pickAllCoins(newState));
107
+ return { ...wallet, state: newState, coinHashes: newCoinHashes };
108
+ },
109
+ replayEventsWithChanges(wallet, secretKeys, events) {
110
+ const stateWithChanges = wallet.state.replayEventsWithChanges(secretKeys, events);
111
+ const newState = stateWithChanges.state;
112
+ const newCoinHashes = CoinHashesMap.updateWithCoins(secretKeys, wallet.coinHashes, CoinHashesMap.pickAllCoins(newState));
113
+ const updatedWallet = { ...wallet, state: newState, coinHashes: newCoinHashes };
114
+ return [updatedWallet, stateWithChanges.changes];
115
+ },
116
+ updateProgress(wallet, { appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }) {
117
+ const updatedProgress = SyncProgress.createSyncProgress({
118
+ appliedIndex: appliedIndex ?? wallet.progress.appliedIndex,
119
+ highestRelevantWalletIndex: highestRelevantWalletIndex ?? wallet.progress.highestRelevantWalletIndex,
120
+ highestIndex: highestIndex ?? wallet.progress.highestIndex,
121
+ highestRelevantIndex: highestRelevantIndex ?? wallet.progress.highestRelevantIndex,
122
+ isConnected: isConnected ?? wallet.progress.isConnected,
123
+ });
124
+ return { ...wallet, progress: updatedProgress };
125
+ },
126
+ // TODO: Remove after tx history is implemented
127
+ addTransaction(wallet, _tx) {
128
+ return wallet;
129
+ },
130
+ /* not implemented until this is done https://shielded.atlassian.net/browse/PM-19678 */
131
+ revertTransaction(wallet, tx) {
132
+ const newState = wallet.state.revertTransaction(tx);
133
+ return { ...wallet, state: newState };
134
+ },
135
+ // TODO: Remove after tx history is implemented
136
+ updateTxHistory(wallet, _newTxs) {
137
+ return wallet;
138
+ },
139
+ spendCoins(wallet, secretKeys, coins, segment) {
140
+ const [offers, newLocalState] = pipe(coins, Arr.reduce([[], wallet.state], ([accOffers, localState], coinToSpend) => {
141
+ const [nextState, newInput] = localState.spend(secretKeys, coinToSpend, segment);
142
+ const inputOffer = ledger.ZswapOffer.fromInput(newInput, coinToSpend.type, coinToSpend.value);
143
+ return [accOffers.concat([inputOffer]), nextState];
144
+ }));
145
+ const updated = { ...wallet, state: newLocalState };
146
+ return [offers, updated];
147
+ },
148
+ watchCoins(wallet, secretKeys, coins) {
149
+ const newLocalState = coins.reduce((localState, coin) => localState.watchFor(wallet.publicKeys.coinPublicKey, coin), wallet.state);
150
+ const newCoinHashes = CoinHashesMap.updateWithNewCoins(secretKeys, wallet.coinHashes, coins);
151
+ return { ...wallet, state: newLocalState, coinHashes: newCoinHashes };
152
+ },
153
+ };
@@ -0,0 +1,8 @@
1
+ import type { CoreWallet } from './CoreWallet.js';
2
+ import { ShieldedAddress, ShieldedCoinPublicKey, ShieldedEncryptionPublicKey } from '@midnightntwrk/wallet-sdk-address-format';
3
+ export type KeysCapability<TState> = {
4
+ getCoinPublicKey(state: TState): ShieldedCoinPublicKey;
5
+ getEncryptionPublicKey(state: TState): ShieldedEncryptionPublicKey;
6
+ getAddress(state: TState): ShieldedAddress;
7
+ };
8
+ export declare const makeDefaultKeysCapability: () => KeysCapability<CoreWallet>;
@@ -0,0 +1,16 @@
1
+ import { ShieldedAddress, ShieldedCoinPublicKey, ShieldedEncryptionPublicKey, } from '@midnightntwrk/wallet-sdk-address-format';
2
+ export const makeDefaultKeysCapability = () => {
3
+ return {
4
+ getCoinPublicKey: (state) => {
5
+ return new ShieldedCoinPublicKey(Buffer.from(state.publicKeys.coinPublicKey, 'hex'));
6
+ },
7
+ getEncryptionPublicKey: (state) => {
8
+ return new ShieldedEncryptionPublicKey(Buffer.from(state.publicKeys.encryptionPublicKey, 'hex'));
9
+ },
10
+ getAddress: (state) => {
11
+ const coinPublicKey = new ShieldedCoinPublicKey(Buffer.from(state.publicKeys.coinPublicKey, 'hex'));
12
+ const encryptionPublicKey = new ShieldedEncryptionPublicKey(Buffer.from(state.publicKeys.encryptionPublicKey, 'hex'));
13
+ return new ShieldedAddress(coinPublicKey, encryptionPublicKey);
14
+ },
15
+ };
16
+ };