@midnightntwrk/wallet-sdk-shielded 4.0.0-beta.2 → 4.0.0-beta.3

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 (60) hide show
  1. package/README.md +11 -6
  2. package/dist/Restore.d.ts +40 -0
  3. package/dist/Restore.js +63 -0
  4. package/dist/ShieldedWallet.d.ts +248 -62
  5. package/dist/ShieldedWallet.js +366 -76
  6. package/dist/ShieldedWalletAPI.d.ts +179 -0
  7. package/dist/ShieldedWalletAPI.js +75 -0
  8. package/dist/SingleVariantShieldedWallet.d.ts +35 -0
  9. package/dist/SingleVariantShieldedWallet.js +240 -0
  10. package/dist/index.d.ts +4 -1
  11. package/dist/index.js +4 -1
  12. package/dist/v1/CoinsAndBalances.d.ts +1 -1
  13. package/dist/v1/CoreWallet.d.ts +48 -1
  14. package/dist/v1/CoreWallet.js +54 -1
  15. package/dist/v1/Migration.d.ts +95 -0
  16. package/dist/v1/Migration.js +82 -0
  17. package/dist/v1/RunningV1Variant.d.ts +1 -1
  18. package/dist/v1/RunningV1Variant.js +37 -8
  19. package/dist/v1/Serialization.js +1 -1
  20. package/dist/v1/Sync.d.ts +132 -9
  21. package/dist/v1/Sync.js +244 -35
  22. package/dist/v1/Transacting.d.ts +1 -1
  23. package/dist/v1/Transacting.js +1 -1
  24. package/dist/v1/TransactionHistory.d.ts +9 -2
  25. package/dist/v1/TransactionHistory.js +26 -5
  26. package/dist/v1/TransactionOps.d.ts +1 -1
  27. package/dist/v1/V1Builder.d.ts +87 -26
  28. package/dist/v1/V1Builder.js +109 -8
  29. package/dist/v1/WalletError.d.ts +1 -1
  30. package/dist/v1/index.d.ts +2 -1
  31. package/dist/v1/index.js +5 -1
  32. package/dist/v2/CoinsAndBalances.d.ts +24 -0
  33. package/dist/v2/CoinsAndBalances.js +41 -0
  34. package/dist/v2/CoreWallet.d.ts +162 -0
  35. package/dist/v2/CoreWallet.js +305 -0
  36. package/dist/v2/Keys.d.ts +8 -0
  37. package/dist/v2/Keys.js +16 -0
  38. package/dist/v2/Migration.d.ts +112 -0
  39. package/dist/v2/Migration.js +96 -0
  40. package/dist/v2/RunningV2Variant.d.ts +40 -0
  41. package/dist/v2/RunningV2Variant.js +127 -0
  42. package/dist/v2/Serialization.d.ts +12 -0
  43. package/dist/v2/Serialization.js +93 -0
  44. package/dist/v2/Sync.d.ts +208 -0
  45. package/dist/v2/Sync.js +436 -0
  46. package/dist/v2/Transacting.d.ts +50 -0
  47. package/dist/v2/Transacting.js +258 -0
  48. package/dist/v2/TransactionHistory.d.ts +100 -0
  49. package/dist/v2/TransactionHistory.js +132 -0
  50. package/dist/v2/TransactionImbalances.d.ts +10 -0
  51. package/dist/v2/TransactionImbalances.js +30 -0
  52. package/dist/v2/TransactionOps.d.ts +17 -0
  53. package/dist/v2/TransactionOps.js +73 -0
  54. package/dist/v2/V2Builder.d.ts +154 -0
  55. package/dist/v2/V2Builder.js +273 -0
  56. package/dist/v2/WalletError.d.ts +74 -0
  57. package/dist/v2/WalletError.js +53 -0
  58. package/dist/v2/index.d.ts +13 -0
  59. package/dist/v2/index.js +26 -0
  60. package/package.json +16 -8
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The contract every shielded wallet composition fulfils, and the state it emits.
3
+ *
4
+ * @remarks
5
+ * Shared by the single-variant composition (`SingleVariantShieldedWallet.ts`) and the one that spans a protocol
6
+ * boundary (`ShieldedWallet.ts`), and kept apart from both so that neither has to import the other to speak the same
7
+ * API. Nothing here knows how many variants are running: a state emission arrives already bound to the capabilities
8
+ * of the variant that produced it, and everything below that binding is plain data.
9
+ */
10
+ import type * as ledger from '@midnightntwrk/ledger-v9';
11
+ import { type AnyTx, type NetworkId, type ProtocolState, ProtocolVersion, type SyncProgress, type UnprovenTx } from '@midnightntwrk/wallet-sdk-abstractions';
12
+ import { type ShieldedAddress, type ShieldedCoinPublicKey, type ShieldedEncryptionPublicKey } from '@midnightntwrk/wallet-sdk-address-format';
13
+ import type { ChainVersionProbe } from '@midnightntwrk/wallet-sdk-capabilities/chainVersion';
14
+ import type { V9UnboundTransaction } from '@midnightntwrk/wallet-sdk-capabilities/proving';
15
+ import { type Duration } from 'effect';
16
+ import type * as rx from 'rxjs';
17
+ import { type CoreWallet as V1CoreWallet } from './v1/CoreWallet.js';
18
+ import { type AvailableCoin, type CoinsAndBalancesCapability, type PendingCoin } from './v2/CoinsAndBalances.js';
19
+ import { type CoreWallet } from './v2/index.js';
20
+ import { type KeysCapability } from './v2/Keys.js';
21
+ import { type SerializationCapability } from './v2/Serialization.js';
22
+ import { type BatchUpdatesConfig, type IndexerClientConnection } from './v2/Sync.js';
23
+ import { type TokenTransfer } from './v2/Transacting.js';
24
+ import { type ShieldedHistoryStorage, type TransactionHistoryService } from './v2/TransactionHistory.js';
25
+ export type ShieldedWalletCapabilities<TSerialized = string> = {
26
+ serialization: SerializationCapability<CoreWallet, null, TSerialized>;
27
+ coinsAndBalances: CoinsAndBalancesCapability<CoreWallet>;
28
+ keys: KeysCapability<CoreWallet>;
29
+ };
30
+ export type ShieldedWalletServices = {
31
+ transactionHistory: TransactionHistoryService;
32
+ };
33
+ /**
34
+ * A transaction that has been proven but not yet bound.
35
+ *
36
+ * @remarks
37
+ * Owned by the proving capability, which is what produces one, and re-exported here so the name stays where callers
38
+ * already reach for it. The shielded wallet neither builds nor consumes one itself.
39
+ */
40
+ export type { V9UnboundTransaction };
41
+ /** The core state of whichever shielded variant produced an emission. */
42
+ export type ShieldedCoreState = V1CoreWallet | CoreWallet;
43
+ /**
44
+ * Everything a state emission projects, already bound to the variant that produced it.
45
+ *
46
+ * @remarks
47
+ * Binding is the point. The capabilities that understand a state and the state itself must be chosen together, in the
48
+ * branch where the producing variant is known; the two variants' capability types are structurally identical, so a
49
+ * capability of one would type-check against a state of the other and be wrong at runtime. Once bound there is
50
+ * nothing left to mis-pair, and everything below is version-agnostic plain data.
51
+ */
52
+ type ShieldedProjections<TSerialized> = Readonly<{
53
+ balances: () => Record<ledger.RawTokenType, bigint>;
54
+ totalCoins: () => readonly (AvailableCoin | PendingCoin)[];
55
+ availableCoins: () => readonly AvailableCoin[];
56
+ pendingCoins: () => readonly PendingCoin[];
57
+ coinPublicKey: () => ShieldedCoinPublicKey;
58
+ encryptionPublicKey: () => ShieldedEncryptionPublicKey;
59
+ address: () => ShieldedAddress;
60
+ serialize: () => TSerialized;
61
+ }>;
62
+ /** The capability set a variant exposes for reading and serializing its own state. */
63
+ type ShieldedStateCapabilities<TState, TSerialized> = Readonly<{
64
+ serialization: SerializationCapability<TState, null, TSerialized>;
65
+ coinsAndBalances: CoinsAndBalancesCapability<TState>;
66
+ keys: KeysCapability<TState>;
67
+ }>;
68
+ export declare class ShieldedWalletState<TSerialized = string, _TTransaction = ledger.FinalizedTransaction> {
69
+ #private;
70
+ /**
71
+ * Wraps a state emission with the capabilities of the variant that produced it.
72
+ *
73
+ * @remarks
74
+ * Call this inside a branch that has narrowed on the emission's `variantTag`, so `variant` and `state` are known to
75
+ * belong together. It is generic over the state type precisely so that pairing is checked.
76
+ */
77
+ static readonly fromVariant: <TState, TSerialized_1 = string>(variant: ShieldedStateCapabilities<TState, TSerialized_1>, state: ProtocolState.ProtocolState<TState>) => ShieldedWalletState<TSerialized_1>;
78
+ readonly protocolVersion: ProtocolVersion.ProtocolVersion;
79
+ readonly state: ShieldedCoreState;
80
+ get balances(): Record<ledger.RawTokenType, bigint>;
81
+ get totalCoins(): readonly (AvailableCoin | PendingCoin)[];
82
+ get availableCoins(): readonly AvailableCoin[];
83
+ get pendingCoins(): readonly PendingCoin[];
84
+ get coinPublicKey(): ShieldedCoinPublicKey;
85
+ get encryptionPublicKey(): ShieldedEncryptionPublicKey;
86
+ get address(): ShieldedAddress;
87
+ get progress(): SyncProgress.SyncProgress;
88
+ constructor(protocolVersion: ProtocolVersion.ProtocolVersion, state: ShieldedCoreState, projections: ShieldedProjections<TSerialized>);
89
+ serialize(): TSerialized;
90
+ }
91
+ /**
92
+ * What balancing a transaction with shielded coins produces: a transaction covering the shortfall, when there is one.
93
+ *
94
+ * @remarks
95
+ * A handle rather than a ledger transaction, because which ledger version built it is the variant's business and not
96
+ * the caller's. Absent when the wallet had nothing to add — the transaction was already balanced on the shielded
97
+ * side.
98
+ */
99
+ export type ShieldedBalancingResult = UnprovenTx | undefined;
100
+ export type ShieldedWalletAPI<TStartAux extends ledger.ZswapSecretKeys = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSerialized = string> = {
101
+ readonly state: rx.Observable<ShieldedWalletState<TSerialized, TTransaction>>;
102
+ start(secretKeys: TStartAux): Promise<void>;
103
+ /**
104
+ * Balances a transaction with shielded coins.
105
+ *
106
+ * @remarks
107
+ * No key material is passed: the wallet derives what its current variant needs from what it was started with. A
108
+ * caller-supplied key object could only ever belong to one ledger version, which is exactly what a wallet spanning
109
+ * a protocol boundary cannot assume.
110
+ */
111
+ balanceTransaction(tx: AnyTx): Promise<ShieldedBalancingResult>;
112
+ transferTransaction(outputs: readonly TokenTransfer[]): Promise<UnprovenTx>;
113
+ initSwap(desiredInputs: Record<ledger.RawTokenType, bigint>, desiredOutputs: readonly TokenTransfer[]): Promise<UnprovenTx>;
114
+ serializeState(): Promise<TSerialized>;
115
+ waitForSyncedState(allowedGap?: bigint): Promise<ShieldedWalletState<TSerialized, TTransaction>>;
116
+ getAddress(): Promise<ShieldedAddress>;
117
+ revertTransaction(transaction: AnyTx): Promise<void>;
118
+ stop(): Promise<void>;
119
+ };
120
+ /**
121
+ * The protocol version a ledger-v9-native chain hands over at.
122
+ *
123
+ * @deprecated Use {@link ProtocolVersion.V9NativeForkVersion} from `@midnightntwrk/wallet-sdk-abstractions` (or the
124
+ * umbrella package) — the value is a property of the chain, not of the shielded wallet, and lives with the version
125
+ * type now. This alias is the same value and will be removed in a later release.
126
+ */
127
+ export declare const V9_NATIVE_FORK_VERSION: ProtocolVersion.ProtocolVersion;
128
+ /**
129
+ * The configuration a default {@link ShieldedWallet} is built from.
130
+ *
131
+ * @remarks
132
+ * Declared by this package rather than aliased to a variant's configuration. A wallet that spans a protocol boundary is
133
+ * built from more than one variant, so no single variant's configuration can be the wallet's public contract: the
134
+ * package states what it asks an application for, and maps it onto whichever variants it registers.
135
+ *
136
+ * The field types are still the ones the variants declare, because they are version-agnostic — `configuration.test.ts`
137
+ * asserts that this type remains interchangeable with what _both_ variants are built from, so a divergence surfaces
138
+ * as a compile error here instead of as a wallet that cannot be built for one of its variants.
139
+ */
140
+ export type DefaultShieldedConfiguration = {
141
+ networkId: NetworkId.NetworkId;
142
+ indexerClientConnection: IndexerClientConnection;
143
+ batchUpdates?: BatchUpdatesConfig;
144
+ txHistoryStorage: ShieldedHistoryStorage;
145
+ transactionDetailsRetryWindow?: Duration.DurationInput;
146
+ /**
147
+ * Where each ledger version begins on this chain: under `v9`, the protocol version at which it hands over from
148
+ * ledger-v8 to ledger-v9.
149
+ *
150
+ * @remarks
151
+ * Required, and deliberately without a default: the wallet registers one variant either side of the boundary, so a
152
+ * wrong value does not degrade — it decides which ledger version reads the chain. Below `forks.v9` the ledger-v8
153
+ * variant is active; from it, the ledger-v9 one. The SDK cannot guess it, because it is a property of the chain the
154
+ * application points at, not of the SDK.
155
+ *
156
+ * A map keyed by ledger version rather than a single number, so the next hard fork adds a key instead of changing the
157
+ * shape of every application's configuration — see {@link ProtocolVersion.ForkSchedule}. A node reporting a 2.x
158
+ * runtime version reports protocol version `2000000`, which is therefore the value for a ledger-v9-native chain —
159
+ * published as `ProtocolVersion.V9NativeForkVersion`. The final mainnet fork constant is not yet fixed; it will
160
+ * join that one once it is, and this field keeps working unchanged.
161
+ */
162
+ forks: ProtocolVersion.ForkSchedule;
163
+ /**
164
+ * How the wallet asks the chain which protocol version its timeline starts under, before it chooses a variant to
165
+ * start at.
166
+ *
167
+ * @remarks
168
+ * Optional, and defaulted rather than absent: left unset, the wallet asks the indexer named by
169
+ * {@link indexerClientConnection}, which it is about to synchronize from anyway. Supply one to ask something else —
170
+ * a cache, a node RPC, a value the application already holds — and have it answer the same question: the version of
171
+ * the chain's **first** block, not its latest. A fresh wallet reads history from the start, so the variant it needs
172
+ * is the one that can deserialize the first event it fetches; a probe reporting the tip of a chain that forked over
173
+ * its own history starts the wallet on a ledger version that cannot read what it is about to be served.
174
+ *
175
+ * The answer is best-effort wherever it comes from: a chain that cannot be reached leaves the wallet starting exactly
176
+ * where it started before there was a probe.
177
+ */
178
+ chainVersionProbe?: ChainVersionProbe;
179
+ };
@@ -0,0 +1,75 @@
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
+ export class ShieldedWalletState {
15
+ /**
16
+ * Wraps a state emission with the capabilities of the variant that produced it.
17
+ *
18
+ * @remarks
19
+ * Call this inside a branch that has narrowed on the emission's `variantTag`, so `variant` and `state` are known to
20
+ * belong together. It is generic over the state type precisely so that pairing is checked.
21
+ */
22
+ static fromVariant = (variant, state) => new ShieldedWalletState(state.version, state.state, {
23
+ balances: () => variant.coinsAndBalances.getAvailableBalances(state.state),
24
+ totalCoins: () => variant.coinsAndBalances.getTotalCoins(state.state),
25
+ availableCoins: () => variant.coinsAndBalances.getAvailableCoins(state.state),
26
+ pendingCoins: () => variant.coinsAndBalances.getPendingCoins(state.state),
27
+ coinPublicKey: () => variant.keys.getCoinPublicKey(state.state),
28
+ encryptionPublicKey: () => variant.keys.getEncryptionPublicKey(state.state),
29
+ address: () => variant.keys.getAddress(state.state),
30
+ serialize: () => variant.serialization.serialize(state.state),
31
+ });
32
+ protocolVersion;
33
+ state;
34
+ #projections;
35
+ get balances() {
36
+ return this.#projections.balances();
37
+ }
38
+ get totalCoins() {
39
+ return this.#projections.totalCoins();
40
+ }
41
+ get availableCoins() {
42
+ return this.#projections.availableCoins();
43
+ }
44
+ get pendingCoins() {
45
+ return this.#projections.pendingCoins();
46
+ }
47
+ get coinPublicKey() {
48
+ return this.#projections.coinPublicKey();
49
+ }
50
+ get encryptionPublicKey() {
51
+ return this.#projections.encryptionPublicKey();
52
+ }
53
+ get address() {
54
+ return this.#projections.address();
55
+ }
56
+ get progress() {
57
+ return this.state.progress;
58
+ }
59
+ constructor(protocolVersion, state, projections) {
60
+ this.protocolVersion = protocolVersion;
61
+ this.state = state;
62
+ this.#projections = projections;
63
+ }
64
+ serialize() {
65
+ return this.#projections.serialize();
66
+ }
67
+ }
68
+ /**
69
+ * The protocol version a ledger-v9-native chain hands over at.
70
+ *
71
+ * @deprecated Use {@link ProtocolVersion.V9NativeForkVersion} from `@midnightntwrk/wallet-sdk-abstractions` (or the
72
+ * umbrella package) — the value is a property of the chain, not of the shielded wallet, and lives with the version
73
+ * type now. This alias is the same value and will be removed in a later release.
74
+ */
75
+ export const V9_NATIVE_FORK_VERSION = ProtocolVersion.V9NativeForkVersion;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A shielded wallet composed from a single variant, which answers for the whole protocol timeline.
3
+ *
4
+ * @remarks
5
+ * The composition for a wallet that is not expected to meet a protocol boundary — a test harness, a simulator-driven
6
+ * wallet, an application pinned to one protocol version. Its one variant is registered from the minimum supported
7
+ * version, so it stamps every transaction at that floor and accepts one of any version; the price is that it cannot
8
+ * cross a protocol boundary. The wallet that can is `ShieldedWallet`, in `ShieldedWallet.ts`.
9
+ */
10
+ import * as ledger from '@midnightntwrk/ledger-v9';
11
+ import { type Variant, type VariantBuilder, type WalletLike } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
12
+ import { type DefaultShieldedConfiguration, type ShieldedWalletAPI } from './ShieldedWalletAPI.js';
13
+ import { type BaseV2Configuration, type DefaultV2Configuration, type V2Variant } from './v2/index.js';
14
+ import { type WalletSyncUpdate } from './v2/Sync.js';
15
+ export type CustomizedShieldedWallet<TStartAux extends ledger.ZswapSecretKeys = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string> = ShieldedWalletAPI<TStartAux, TTransaction, TSerialized> & WalletLike.WalletLike<[Variant.VersionedVariant<V2Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>]>;
16
+ export interface CustomizedShieldedWalletClass<TStartAux extends ledger.ZswapSecretKeys = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string, TConfig extends BaseV2Configuration = DefaultShieldedConfiguration> extends WalletLike.BaseWalletClass<[
17
+ Variant.VersionedVariant<V2Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>>
18
+ ]> {
19
+ configuration: TConfig;
20
+ startWithSeed(seed: Uint8Array): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
21
+ startWithSecretKeys(secretKeys: ledger.ZswapSecretKeys): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
22
+ restore(serializedState: TSerialized): CustomizedShieldedWallet<TStartAux, TTransaction, TSyncUpdate, TSerialized>;
23
+ }
24
+ /**
25
+ * Builds a shielded wallet class over a single variant.
26
+ *
27
+ * @remarks
28
+ * One variant, and therefore one ledger version: this is the composition for a wallet that is not expected to meet a
29
+ * protocol boundary — a test harness, a simulator-driven wallet, an application pinned to one protocol version. The
30
+ * wallet this package ships is `ShieldedWallet`, which registers a variant either side of the boundary.
31
+ * @param configuration What the variant is built from.
32
+ * @param builder The variant builder.
33
+ * @returns The wallet class.
34
+ */
35
+ export declare function CustomShieldedWallet<TConfig extends BaseV2Configuration = DefaultV2Configuration, TStartAux extends ledger.ZswapSecretKeys = ledger.ZswapSecretKeys, TTransaction = ledger.FinalizedTransaction, TSyncUpdate = WalletSyncUpdate, TSerialized = string>(configuration: TConfig, builder: VariantBuilder.VariantBuilder<V2Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux>, TConfig>): CustomizedShieldedWalletClass<TStartAux, TTransaction, TSyncUpdate, TSerialized, TConfig>;
@@ -0,0 +1,240 @@
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
+ /**
14
+ * A shielded wallet composed from a single variant, which answers for the whole protocol timeline.
15
+ *
16
+ * @remarks
17
+ * The composition for a wallet that is not expected to meet a protocol boundary — a test harness, a simulator-driven
18
+ * wallet, an application pinned to one protocol version. Its one variant is registered from the minimum supported
19
+ * version, so it stamps every transaction at that floor and accepts one of any version; the price is that it cannot
20
+ * cross a protocol boundary. The wallet that can is `ShieldedWallet`, in `ShieldedWallet.ts`.
21
+ */
22
+ import * as ledger from '@midnightntwrk/ledger-v9';
23
+ import { ProtocolVersion, WalletSeed, WalletTransaction, } from '@midnightntwrk/wallet-sdk-abstractions';
24
+ import { WalletBuilder } from '@midnightntwrk/wallet-sdk-runtime';
25
+ import { StartMaterial, } from '@midnightntwrk/wallet-sdk-runtime/abstractions';
26
+ import { EitherOps, HList, Poly } from '@midnightntwrk/wallet-sdk-utilities';
27
+ import { Effect, Either, Option, Ref } from 'effect';
28
+ import * as rx from 'rxjs';
29
+ import { variantForSnapshot } from './Restore.js';
30
+ import { ShieldedWalletState, } from './ShieldedWalletAPI.js';
31
+ import { V2Tag, CoreWallet, } from './v2/index.js';
32
+ /**
33
+ * Builds a shielded wallet class over a single variant.
34
+ *
35
+ * @remarks
36
+ * One variant, and therefore one ledger version: this is the composition for a wallet that is not expected to meet a
37
+ * protocol boundary — a test harness, a simulator-driven wallet, an application pinned to one protocol version. The
38
+ * wallet this package ships is `ShieldedWallet`, which registers a variant either side of the boundary.
39
+ * @param configuration What the variant is built from.
40
+ * @param builder The variant builder.
41
+ * @returns The wallet class.
42
+ */
43
+ export function CustomShieldedWallet(configuration, builder) {
44
+ const buildArgs = [configuration];
45
+ const BaseWallet = WalletBuilder.init()
46
+ .withVariant(ProtocolVersion.MinSupportedVersion, builder)
47
+ .build(...buildArgs);
48
+ return class CustomShieldedWalletImplementation extends BaseWallet {
49
+ static startWithSecretKeys(secretKeys) {
50
+ return CustomShieldedWalletImplementation.startFirst(CustomShieldedWalletImplementation, CoreWallet.initEmpty(secretKeys, CustomShieldedWalletImplementation.configuration.networkId));
51
+ }
52
+ /**
53
+ * Builds a wallet from a seed, and remembers the seed.
54
+ *
55
+ * @remarks
56
+ * The seed is the only key material that crosses a protocol boundary, so a wallet built this way can start
57
+ * synchronization on any variant it is ever migrated to — each derives its own from the seed. A wallet built from
58
+ * key objects instead can only start the variants it was given objects for.
59
+ */
60
+ static startWithSeed(seed) {
61
+ const walletSeed = WalletSeed.WalletSeed(seed);
62
+ const secretKeys = ledger.ZswapSecretKeys.fromSeed(walletSeed);
63
+ const wallet = CustomShieldedWalletImplementation.startWithSecretKeys(secretKeys);
64
+ wallet.#retainSeed(walletSeed);
65
+ return wallet;
66
+ }
67
+ /**
68
+ * Restores a wallet from a snapshot, into whichever registered variant wrote it.
69
+ *
70
+ * @remarks
71
+ * The snapshot declares the protocol version it was written at, so the variant that can read it is a lookup rather
72
+ * than an assumption. A snapshot written before snapshots declared a version, or a serialization format this
73
+ * wallet does not recognise as an envelope at all, restores into the head variant — which is what every restore
74
+ * did before there was more than one variant to choose between.
75
+ * @param serializedState The serialized wallet state.
76
+ * @returns A wallet started from that state, on the variant that owns its protocol version.
77
+ * @throws UnsupportedSnapshotVersionError if the snapshot declares a version no registered variant reads.
78
+ */
79
+ static restore(serializedState) {
80
+ const headVariant = HList.head(CustomShieldedWalletImplementation.allVariants());
81
+ const routed =
82
+ // Routing reads a serialized envelope, which only a wallet keeping the default string serialization has. A
83
+ // custom format is left with the behaviour it has always had.
84
+ typeof serializedState === 'string'
85
+ ? variantForSnapshot(serializedState, (version) => CustomShieldedWalletImplementation.variantFor(version), headVariant)
86
+ : Either.right(headVariant);
87
+ const variant = routed.pipe(Either.getOrThrow);
88
+ const deserialized = variant.variant.deserializeState(serializedState).pipe(Either.getOrThrow);
89
+ return CustomShieldedWalletImplementation.startAtVariant(CustomShieldedWalletImplementation, variant, deserialized);
90
+ }
91
+ state;
92
+ /**
93
+ * What the application started this wallet with, kept so synchronization can be started again.
94
+ *
95
+ * @remarks
96
+ * A migration starts a fresh variant whose sync has never run, and sync needs key material. That material cannot
97
+ * come from the state — it is deliberately absent from anything serialized — and does not exist when the wallet
98
+ * is first constructed, so it is held here, in memory, for the lifetime of the wallet. Cleared by {@link stop} so
99
+ * a stopped wallet cannot be silently resurrected by a late activation.
100
+ *
101
+ * A retained seed answers for every variant. Retained key objects answer only for the variants they were supplied
102
+ * for, and accumulate per variant tag as `start` is called, which is the same product a caller holding key
103
+ * objects for both protocol versions would hand over at once.
104
+ */
105
+ #retainedStartMaterial = Ref.unsafeMake(Option.none());
106
+ /** Remembers a seed, which supersedes any key objects retained for individual variants. */
107
+ #retainSeed(seed) {
108
+ Ref.set(this.#retainedStartMaterial, Option.some(StartMaterial.fromSeed(seed))).pipe(Effect.runSync);
109
+ }
110
+ /**
111
+ * Starts synchronization on a variant that has just become current, with key material it can use.
112
+ *
113
+ * @remarks
114
+ * The derivation is the activating variant's own, so a wallet retaining a seed hands each variant key material
115
+ * built by its own ledger version. A wallet retaining key objects for other variants only cannot answer, and says
116
+ * so rather than handing over keys the variant would silently misuse.
117
+ */
118
+ #resumeSyncOn(variantTag, running) {
119
+ return Ref.get(this.#retainedStartMaterial).pipe(Effect.flatMap(Option.match({
120
+ // Stopped, or never started: there is nothing to resume and nothing to resume it with.
121
+ onNone: () => Effect.void,
122
+ onSome: (retained) => EitherOps.toEffect(StartMaterial.requireAuxFor(retained, variantTag, (seed) => CustomShieldedWalletImplementation.allVariantsRecord()[variantTag].variant.startAux.fromSeed(seed))).pipe(Effect.flatMap((aux) => running.startSyncInBackground(aux))),
123
+ })));
124
+ }
125
+ /**
126
+ * Whether the activation watcher has been registered.
127
+ *
128
+ * @remarks
129
+ * Registration is per wallet, not per `start`: watchers accumulate, so registering on every call would restart sync
130
+ * once per historical `start` on the next activation. Flipped with `getAndSet` so concurrent `start` calls cannot
131
+ * both observe it unset.
132
+ */
133
+ #watcherRegistered = Ref.unsafeMake(false);
134
+ constructor(runtime, scope) {
135
+ super(runtime, scope);
136
+ this.state = this.rawState.pipe(rx.map((emission) =>
137
+ // One variant, so the pairing is trivial here; the forking wallet narrows on `variantTag` first.
138
+ ShieldedWalletState.fromVariant(CustomShieldedWalletImplementation.allVariantsRecord()[V2Tag].variant, emission)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
139
+ }
140
+ start(secretKeys) {
141
+ return Effect.gen(this, function* () {
142
+ const current = yield* this.runtime.currentVariant;
143
+ yield* Ref.update(this.#retainedStartMaterial, (retained) => Option.some(Option.match(retained, {
144
+ onNone: () => StartMaterial.forVariant(Poly.getTag(current), secretKeys),
145
+ // A retained seed already answers for every variant, including ones this wallet has not met, so key
146
+ // objects for one of them add nothing. Otherwise the objects accumulate per variant tag.
147
+ onSome: (existing) => existing._tag === 'FromSeed'
148
+ ? existing
149
+ : StartMaterial.forVariants([...existing.byTag, [Poly.getTag(current), secretKeys]]),
150
+ })));
151
+ // Registered before the first dispatch, and only once: `onVariantActivation` resolves only after its
152
+ // subscription is live, so an activation racing this call is queued rather than missed.
153
+ const alreadyRegistered = yield* Ref.getAndSet(this.#watcherRegistered, true);
154
+ if (!alreadyRegistered) {
155
+ yield* this.runtime.onVariantActivation({
156
+ [V2Tag]: (v2) => this.#resumeSyncOn(V2Tag, v2),
157
+ });
158
+ }
159
+ yield* this.runtime.dispatch({ [V2Tag]: (v2) => v2.startSyncInBackground(secretKeys) });
160
+ }).pipe(Effect.runPromise);
161
+ }
162
+ async stop() {
163
+ // Released before the runtime is torn down: the key material outlives neither the wallet nor an in-flight
164
+ // activation.
165
+ Ref.set(this.#retainedStartMaterial, Option.none()).pipe(Effect.runSync);
166
+ await super.stop();
167
+ }
168
+ /**
169
+ * The key material this wallet's one variant uses, from what it was started with.
170
+ *
171
+ * @remarks
172
+ * Typed as the variant's own start-aux, which for a single-variant wallet is whatever the application handed to
173
+ * `start` — there is no other variant to derive for, so a retained seed is only ever used through the variant's
174
+ * own derivation.
175
+ */
176
+ #requireAux() {
177
+ return Effect.gen(this, function* () {
178
+ const current = yield* this.runtime.currentVariant;
179
+ const variantTag = Poly.getTag(current);
180
+ const retained = yield* Ref.get(this.#retainedStartMaterial);
181
+ return yield* Option.match(retained, {
182
+ onNone: () => Effect.fail(new StartMaterial.MissingStartAuxError({
183
+ message: `This wallet holds no key material: it has not been started, or it has been stopped. Start it ` +
184
+ `before asking it to build a transaction.`,
185
+ variantTag,
186
+ })),
187
+ onSome: (material) => EitherOps.toEffect(StartMaterial.requireAuxFor(material, variantTag, (seed) => CustomShieldedWalletImplementation.allVariantsRecord()[V2Tag].variant.startAux.fromSeed(seed))),
188
+ });
189
+ });
190
+ }
191
+ /** The whole of the protocol timeline: one variant answers for every version this wallet will ever see. */
192
+ static #epoch = ProtocolVersion.epochOf(ProtocolVersion.MinSupportedVersion, ProtocolVersion.MinSupportedVersion);
193
+ balanceTransaction(tx) {
194
+ return this.runtime
195
+ .dispatch({
196
+ [V2Tag]: (v2) => Effect.all([
197
+ this.#requireAux(),
198
+ EitherOps.toEffect(WalletTransaction.unwrapWithin(tx, CustomShieldedWalletImplementation.#epoch)),
199
+ ]).pipe(Effect.flatMap(([keys, unwrapped]) => v2.balanceTransaction(keys, unwrapped)), Effect.map((result) => result === undefined
200
+ ? undefined
201
+ : WalletTransaction.adopt('Unproven', result, ProtocolVersion.MinSupportedVersion))),
202
+ })
203
+ .pipe(Effect.runPromise);
204
+ }
205
+ transferTransaction(outputs) {
206
+ return this.runtime
207
+ .dispatch({
208
+ [V2Tag]: (v2) => this.#requireAux().pipe(Effect.flatMap((keys) => v2.transferTransaction(keys, outputs)), Effect.map((tx) => WalletTransaction.adopt('Unproven', tx, ProtocolVersion.MinSupportedVersion))),
209
+ })
210
+ .pipe(Effect.runPromise);
211
+ }
212
+ initSwap(desiredInputs, desiredOutputs) {
213
+ return this.runtime
214
+ .dispatch({
215
+ [V2Tag]: (v2) => this.#requireAux().pipe(Effect.flatMap((keys) => v2.initSwap(keys, desiredInputs, desiredOutputs)), Effect.map((tx) => WalletTransaction.adopt('Unproven', tx, ProtocolVersion.MinSupportedVersion))),
216
+ })
217
+ .pipe(Effect.runPromise);
218
+ }
219
+ revertTransaction(transaction) {
220
+ return this.runtime
221
+ .dispatch({
222
+ [V2Tag]: (v2) => Either.match(WalletTransaction.unwrapWithin(transaction, CustomShieldedWalletImplementation.#epoch), { onLeft: () => Effect.void, onRight: (unwrapped) => v2.revertTransaction(unwrapped) }),
223
+ })
224
+ .pipe(Effect.runPromise);
225
+ }
226
+ waitForSyncedState(allowedGap = 0n) {
227
+ return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
228
+ }
229
+ /**
230
+ * Serializes the most recent state It's preferable to use [[ShieldedWalletState.serialize]] instead, to know
231
+ * exactly, which state is serialized
232
+ */
233
+ serializeState() {
234
+ return rx.firstValueFrom(this.state).then((state) => state.serialize());
235
+ }
236
+ getAddress() {
237
+ return rx.firstValueFrom(this.state).then((state) => state.address);
238
+ }
239
+ };
240
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
+ export * from './ShieldedWalletAPI.js';
2
+ export * from './SingleVariantShieldedWallet.js';
1
3
  export * from './ShieldedWallet.js';
2
- export { type ShieldedTransactionHistoryEntry, ShieldedSectionSchema, mergeShieldedSections, } from './v1/TransactionHistory.js';
4
+ export * as Restore from './Restore.js';
5
+ export { type ShieldedTransactionHistoryEntry, ShieldedSectionSchema, mergeShieldedSections, } from './v2/TransactionHistory.js';
package/dist/index.js CHANGED
@@ -10,5 +10,8 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
+ export * from './ShieldedWalletAPI.js';
14
+ export * from './SingleVariantShieldedWallet.js';
13
15
  export * from './ShieldedWallet.js';
14
- export { ShieldedSectionSchema, mergeShieldedSections, } from './v1/TransactionHistory.js';
16
+ export * as Restore from './Restore.js';
17
+ export { ShieldedSectionSchema, mergeShieldedSections, } from './v2/TransactionHistory.js';
@@ -1,5 +1,5 @@
1
1
  import { type CoreWallet } from './CoreWallet.js';
2
- import type * as ledger from '@midnightntwrk/ledger-v9';
2
+ import type * as ledger from '@midnight-ntwrk/ledger-v8';
3
3
  export type AvailableCoin = {
4
4
  coin: ledger.QualifiedShieldedCoinInfo;
5
5
  commitment: ledger.CoinCommitment;
@@ -1,4 +1,4 @@
1
- import * as ledger from '@midnightntwrk/ledger-v9';
1
+ import * as ledger from '@midnight-ntwrk/ledger-v8';
2
2
  import { ProtocolVersion, SyncProgress } from '@midnightntwrk/wallet-sdk-abstractions';
3
3
  import { Either } from 'effect';
4
4
  import { type WalletError } from './WalletError.js';
@@ -35,6 +35,53 @@ export declare const CoreWallet: {
35
35
  restore(localState: ledger.ZswapLocalState, secretKeys: ledger.ZswapSecretKeys, syncProgress: Omit<SyncProgress.SyncProgressData, "isConnected">, protocolVersion: bigint, networkId: string): CoreWallet;
36
36
  restoreWithCoinHashes(publicKeys: PublicKeys, localState: ledger.ZswapLocalState, coinHashes: CoinHashesMap, syncProgress: SyncProgress.SyncProgressData, protocolVersion: bigint, networkId: string): Either.Either<CoreWallet, WalletError>;
37
37
  initEmpty(keys: ledger.ZswapSecretKeys, networkId: string): CoreWallet;
38
+ /**
39
+ * Records an observed protocol version on the wallet, never going backwards.
40
+ *
41
+ * @remarks
42
+ * The version is a signal, not a measurement: writing one outside the running variant's activation range is what
43
+ * makes it hand over to the next variant. A source that briefly reports an older version — a reconnect replaying
44
+ * from an earlier cursor, say — must therefore not be able to drag the wallet back below a boundary it has already
45
+ * crossed, which would ask the runtime to migrate backwards. Taking the maximum makes that unrepresentable.
46
+ * @param wallet The wallet to annotate.
47
+ * @param version The protocol version just observed.
48
+ * @returns `wallet` unchanged if it already records `version` or a later one, otherwise a copy recording `version`.
49
+ */
50
+ withProtocolVersion(wallet: CoreWallet, version: ProtocolVersion.ProtocolVersion): CoreWallet;
51
+ /**
52
+ * Projects a wallet inherited from the previous ledger version onto a fresh state of this one.
53
+ *
54
+ * @remarks
55
+ * No coin data crosses the boundary here, and that is a decision rather than an oversight. This is the oldest variant
56
+ * the wallet registers and no ledger version below it exists, so nothing ever hands a state to this projection: it
57
+ * is shape parity with the twin at `src/v2`, which is the variant a real crossing lands in. That twin adopts the
58
+ * previous wallet's local state whole, by deserializing its bytes — the two ledger majors share the
59
+ * `zswap-local-state` codec — because the chain's state translation continues the commitment tree across the fork
60
+ * and the indexer re-emits none of the ledger-v8 timeline (see `src/v2/Migration.ts`). Mirroring that into a seam
61
+ * no chain can reach would buy nothing, so this side stays as it is.
62
+ *
63
+ * What crosses is therefore identity and position: the public keys — which decide whose coins the far side can
64
+ * decrypt — the network, the protocol version that triggered the hand-over, kept so the new variant starts inside
65
+ * its own activation range rather than immediately signalling backwards, and the cursor the previous variant
66
+ * stopped at.
67
+ *
68
+ * **Sync progress is parked at the fork, not rewound**: the previous wallet's cursor crosses unchanged. A fork does
69
+ * not restart the timeline — event ids run onwards from whatever the indexer had reached when it happened, never
70
+ * from zero — so resuming from the inherited cursor is what puts this wallet in front of what comes next. Rewinding
71
+ * to zero would park it on a stretch of history that this ledger version's events do not occupy. What does not
72
+ * cross is `isConnected`: no sync is running behind this state yet.
73
+ *
74
+ * Coin hashes start empty for the same reason the tree does: they are commitments and nullifiers computed under the
75
+ * previous ledger's codec, and this version recomputes its own from the keys and whatever state it ends up with.
76
+ * @param previous The plain data read off the previous ledger version's wallet.
77
+ * @returns A wallet of this ledger version holding no coins, positioned at the fork.
78
+ */
79
+ fromPreviousVersion(previous: {
80
+ readonly publicKeys: PublicKeys;
81
+ readonly networkId: string;
82
+ readonly protocolVersion: bigint;
83
+ readonly progress: SyncProgress.SyncProgressData;
84
+ }): CoreWallet;
38
85
  applyCollapsedUpdate(wallet: CoreWallet, collapsed: ledger.MerkleTreeCollapsedUpdate): CoreWallet;
39
86
  apply<TOffer extends ledger.ZswapOffer<ledger.Proofish>>(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, offer: TOffer): CoreWallet;
40
87
  replayEventsWithChanges(wallet: CoreWallet, secretKeys: ledger.ZswapSecretKeys, events: ledger.Event[]): [CoreWallet, ledger.ZswapStateChanges[]];