@midnight-ntwrk/wallet-sdk-capabilities 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 (68) hide show
  1. package/README.md +27 -3
  2. package/dist/chainVersion/chainVersionProbe.d.ts +95 -0
  3. package/dist/chainVersion/chainVersionProbe.js +83 -0
  4. package/dist/chainVersion/index.d.ts +1 -0
  5. package/dist/chainVersion/index.js +13 -0
  6. package/dist/codecs/index.d.ts +1 -0
  7. package/dist/codecs/index.js +13 -0
  8. package/dist/codecs/ledgerParameters.d.ts +81 -0
  9. package/dist/codecs/ledgerParameters.js +63 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +3 -0
  12. package/dist/pendingTransactions/pendingTransactions.d.ts +113 -9
  13. package/dist/pendingTransactions/pendingTransactions.js +147 -30
  14. package/dist/pendingTransactions/pendingTransactionsService.d.ts +25 -9
  15. package/dist/pendingTransactions/pendingTransactionsService.js +33 -21
  16. package/dist/proving/index.d.ts +2 -0
  17. package/dist/proving/index.js +2 -0
  18. package/dist/proving/provingService.d.ts +175 -15
  19. package/dist/proving/provingService.js +104 -11
  20. package/dist/proving/v8ProvingService.d.ts +53 -0
  21. package/dist/proving/v8ProvingService.js +71 -0
  22. package/dist/proving/versionedProving.d.ts +42 -0
  23. package/dist/proving/versionedProving.js +103 -0
  24. package/dist/signatures/index.d.ts +2 -0
  25. package/dist/signatures/index.js +14 -0
  26. package/dist/signatures/signing.d.ts +37 -0
  27. package/dist/signatures/signing.js +13 -0
  28. package/dist/signatures/v8Signatures.d.ts +54 -0
  29. package/dist/signatures/v8Signatures.js +62 -0
  30. package/dist/simulation/ForkSimulator.d.ts +114 -0
  31. package/dist/simulation/ForkSimulator.js +209 -0
  32. package/dist/simulation/LedgerTranslation.d.ts +53 -0
  33. package/dist/simulation/LedgerTranslation.js +56 -0
  34. package/dist/simulation/core/VersionTimeline.d.ts +54 -0
  35. package/dist/simulation/core/VersionTimeline.js +56 -0
  36. package/dist/simulation/core/blocks.d.ts +38 -0
  37. package/dist/simulation/core/blocks.js +52 -0
  38. package/dist/simulation/core/index.d.ts +13 -0
  39. package/dist/simulation/core/index.js +25 -0
  40. package/dist/simulation/core/strictness.d.ts +29 -0
  41. package/dist/simulation/core/strictness.js +39 -0
  42. package/dist/simulation/index.d.ts +22 -2
  43. package/dist/simulation/index.js +25 -14
  44. package/dist/simulation/v8/Simulator.d.ts +231 -0
  45. package/dist/simulation/v8/Simulator.js +503 -0
  46. package/dist/simulation/{SimulatorState.d.ts → v8/SimulatorState.d.ts} +31 -44
  47. package/dist/simulation/v8/SimulatorState.js +290 -0
  48. package/dist/simulation/v8/index.d.ts +2 -0
  49. package/dist/simulation/v8/index.js +26 -0
  50. package/dist/simulation/{Simulator.d.ts → v9/Simulator.d.ts} +57 -6
  51. package/dist/simulation/{Simulator.js → v9/Simulator.js} +68 -18
  52. package/dist/simulation/v9/SimulatorState.d.ts +336 -0
  53. package/dist/simulation/{SimulatorState.js → v9/SimulatorState.js} +34 -67
  54. package/dist/simulation/v9/index.d.ts +2 -0
  55. package/dist/simulation/v9/index.js +26 -0
  56. package/dist/submission/submissionService.d.ts +2 -1
  57. package/dist/submission/submissionService.js +1 -1
  58. package/dist/validation/blockData.d.ts +42 -2
  59. package/dist/validation/blockData.js +55 -8
  60. package/dist/validation/index.d.ts +2 -0
  61. package/dist/validation/index.js +2 -0
  62. package/dist/validation/v8ValidationService.d.ts +29 -0
  63. package/dist/validation/v8ValidationService.js +48 -0
  64. package/dist/validation/validationService.d.ts +132 -17
  65. package/dist/validation/validationService.js +88 -42
  66. package/dist/validation/versionedValidation.d.ts +46 -0
  67. package/dist/validation/versionedValidation.js +55 -0
  68. package/package.json +23 -9
@@ -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 * as Signing from './signing.js';
14
+ export * from './v8Signatures.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How the SDK writes a signature, whichever ledger version is underneath.
3
+ *
4
+ * @remarks
5
+ * The one scalar that genuinely changed shape at the protocol boundary. Ledger-v8 has a single signature scheme and
6
+ * writes a signature as bare hexadecimal; ledger-v9 has more than one and names the scheme alongside the bytes.
7
+ * Everything else an application reads — token types, addresses, nullifiers, transaction identifiers — is
8
+ * byte-identical across the two.
9
+ *
10
+ * The SDK speaks the ledger-v9 shape everywhere and lowers it for the V1 variant, rather than the reverse. Lifting is
11
+ * total — ledger-v8 has exactly one scheme, so naming it is never a guess — while lowering is partial, and a scheme
12
+ * ledger-v8 has never heard of is refused rather than handed over as bytes it would misread. Speaking the ledger-v8
13
+ * shape would have made the common case lossy instead.
14
+ *
15
+ * Kept beside that lifting and lowering rather than among the abstractions a variant implements, because this is the
16
+ * vocabulary those two functions are stated in and nothing else in the SDK is defined against it. It names no ledger
17
+ * version itself, so an application can annotate a signer without importing one, and nothing on the way to it loads
18
+ * either ledger's WebAssembly. These are structurally ledger-v9's own types: a signer already written against them
19
+ * compiles unchanged.
20
+ */
21
+ /** The signature schemes the chain knows. Only `schnorr` exists before the protocol boundary. */
22
+ export type SignatureKind = 'schnorr' | 'ecdsa';
23
+ /** A signature: the scheme that produced it, and its bytes as hexadecimal. */
24
+ export type Signature = Readonly<{
25
+ tag: SignatureKind;
26
+ value: string;
27
+ }>;
28
+ /** The public half of a signing key, named with the scheme it belongs to. */
29
+ export type SignatureVerifyingKey = Readonly<{
30
+ tag: SignatureKind;
31
+ value: string;
32
+ }>;
33
+ /** A signing key, named with the scheme it belongs to. */
34
+ export type SigningKey = Readonly<{
35
+ tag: SignatureKind;
36
+ value: string;
37
+ }>;
@@ -0,0 +1,13 @@
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 {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Signatures and verifying keys across a protocol boundary.
3
+ *
4
+ * @remarks
5
+ * The one scalar that genuinely changed shape at the fork. Ledger-v8 has a single signature scheme and writes a
6
+ * signature as bare hex; ledger-v9 names the scheme alongside the bytes, because it has more than one. So the SDK
7
+ * speaks the ledger-v9 shape everywhere and lowers it for the V1 variant, which is a total operation in one direction
8
+ * and a partial one in the other: a signature of a scheme ledger-v8 has never heard of cannot be lowered at all, and
9
+ * says so rather than being handed over as bytes it would misread.
10
+ */
11
+ import type * as ledgerV8 from '@midnight-ntwrk/ledger-v8';
12
+ import { Either } from 'effect';
13
+ import type * as Signing from './signing.js';
14
+ declare const UnsupportedSignatureKindError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
15
+ readonly _tag: "@midnight-ntwrk/wallet-sdk-capabilities/signatures/v8Signatures/UnsupportedSignatureKindError";
16
+ } & Readonly<A>;
17
+ /** Raised when a signature or verifying key names a scheme ledger-v8 does not have. */
18
+ export declare class UnsupportedSignatureKindError extends UnsupportedSignatureKindError_base<{
19
+ readonly message: string;
20
+ /** The scheme that was named. */
21
+ readonly kind: Signing.SignatureKind;
22
+ }> {
23
+ }
24
+ /**
25
+ * Lowers a signature to ledger-v8's shape.
26
+ *
27
+ * @param signature The signature, as ledger-v9 writes it.
28
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
29
+ */
30
+ export declare const lowerSignature: (signature: Signing.Signature) => Either.Either<ledgerV8.Signature, UnsupportedSignatureKindError>;
31
+ /**
32
+ * Lowers a signature verifying key to ledger-v8's shape.
33
+ *
34
+ * @param key The verifying key, as ledger-v9 writes it.
35
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
36
+ */
37
+ export declare const lowerSignatureVerifyingKey: (key: Signing.SignatureVerifyingKey) => Either.Either<ledgerV8.SignatureVerifyingKey, UnsupportedSignatureKindError>;
38
+ /**
39
+ * Lifts a ledger-v8 signature into ledger-v9's shape.
40
+ *
41
+ * @remarks
42
+ * Total, unlike lowering: ledger-v8 has exactly one scheme, so naming it is never a guess.
43
+ * @param signature The signature, as ledger-v8 writes it.
44
+ * @returns The same signature, with the scheme it necessarily used named.
45
+ */
46
+ export declare const liftSignature: (signature: ledgerV8.Signature) => Signing.Signature;
47
+ /**
48
+ * Lifts a ledger-v8 verifying key into ledger-v9's shape.
49
+ *
50
+ * @param key The verifying key, as ledger-v8 writes it.
51
+ * @returns The same key, with the scheme it necessarily used named.
52
+ */
53
+ export declare const liftSignatureVerifyingKey: (key: ledgerV8.SignatureVerifyingKey) => Signing.SignatureVerifyingKey;
54
+ export {};
@@ -0,0 +1,62 @@
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 { Data, Either } from 'effect';
14
+ /** The signature scheme ledger-v8 has, and the only one a lowered value can name. */
15
+ const V8_SIGNATURE_KIND = 'schnorr';
16
+ /** Raised when a signature or verifying key names a scheme ledger-v8 does not have. */
17
+ export class UnsupportedSignatureKindError extends Data.TaggedError('@midnight-ntwrk/wallet-sdk-capabilities/signatures/v8Signatures/UnsupportedSignatureKindError') {
18
+ }
19
+ const lower = (value, what) => value.tag === V8_SIGNATURE_KIND
20
+ ? Either.right(value.value)
21
+ : Either.left(new UnsupportedSignatureKindError({
22
+ message: `This ${what} names the ${value.tag} signature scheme, which the ledger version before the protocol ` +
23
+ `boundary does not have: it has ${V8_SIGNATURE_KIND} and nothing else. Sign with ` +
24
+ `${V8_SIGNATURE_KIND} while the chain is still on ledger-v8.`,
25
+ kind: value.tag,
26
+ }));
27
+ /**
28
+ * Lowers a signature to ledger-v8's shape.
29
+ *
30
+ * @param signature The signature, as ledger-v9 writes it.
31
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
32
+ */
33
+ export const lowerSignature = (signature) => lower(signature, 'signature');
34
+ /**
35
+ * Lowers a signature verifying key to ledger-v8's shape.
36
+ *
37
+ * @param key The verifying key, as ledger-v9 writes it.
38
+ * @returns The bare hex ledger-v8 reads, or the reason it cannot be expressed there.
39
+ */
40
+ export const lowerSignatureVerifyingKey = (key) => lower(key, 'verifying key');
41
+ /**
42
+ * Lifts a ledger-v8 signature into ledger-v9's shape.
43
+ *
44
+ * @remarks
45
+ * Total, unlike lowering: ledger-v8 has exactly one scheme, so naming it is never a guess.
46
+ * @param signature The signature, as ledger-v8 writes it.
47
+ * @returns The same signature, with the scheme it necessarily used named.
48
+ */
49
+ export const liftSignature = (signature) => ({
50
+ tag: V8_SIGNATURE_KIND,
51
+ value: signature,
52
+ });
53
+ /**
54
+ * Lifts a ledger-v8 verifying key into ledger-v9's shape.
55
+ *
56
+ * @param key The verifying key, as ledger-v8 writes it.
57
+ * @returns The same key, with the scheme it necessarily used named.
58
+ */
59
+ export const liftSignatureVerifyingKey = (key) => ({
60
+ tag: V8_SIGNATURE_KIND,
61
+ value: key,
62
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A simulated chain that crosses a hard fork.
3
+ *
4
+ * Ledger-v8 the chain runs on ledger-v8, ledger-v9 on ledger-v9, with real ledger bytes on both sides — enough for a
5
+ * wallet under test to observe the version change, migrate, and go on transacting.
6
+ */
7
+ import { Effect, Option, Scope, type Array as Arr } from 'effect';
8
+ import { NetworkId, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
9
+ import { type LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
10
+ import { LedgerTranslationError, type LedgerStateTranslator } from './LedgerTranslation.js';
11
+ import { Simulator } from './v9/Simulator.js';
12
+ import { type BlockProducer } from './v9/SimulatorState.js';
13
+ import * as V8 from './v8/index.js';
14
+ /** {@link ForkSimulator} initialization configuration. */
15
+ export type ForkSimulatorConfig = Readonly<{
16
+ /**
17
+ * Height at which the chain forks.
18
+ *
19
+ * The ledger-v8 chain's block at this height is a ledger-v8 block stamped with {@link forkVersion} — the signal a
20
+ * wallet's V1 variant migrates on, which it observes but does not apply. The ledger-v9 chain then begins at the same
21
+ * height, re-delivering it with ledger-v9 content, mirroring a wallet re-fetching the boundary with its new codec.
22
+ */
23
+ forkBlock: bigint;
24
+ /**
25
+ * Protocol version the fork activates.
26
+ *
27
+ * Required, and never defaulted: the protocol version of the real fork is not final, so every caller states which
28
+ * version it means.
29
+ */
30
+ forkVersion: ProtocolVersion.ProtocolVersion;
31
+ /**
32
+ * How the ledger-v8 chain's ledger state becomes the ledger-v9 chain's starting point.
33
+ *
34
+ * Receives the final ledger-v8 state, serialized, and returns the ledger-v9 one. See {@link LedgerStateTranslator} for
35
+ * why the seam is stated in bytes rather than state objects.
36
+ */
37
+ translator: LedgerStateTranslator;
38
+ /** Protocol version the ledger-v8 chain runs on. Defaults to {@link ProtocolVersion.MinSupportedVersion}. */
39
+ v8Version?: ProtocolVersion.ProtocolVersion;
40
+ /** Network identifier, shared by both chains. Defaults to Undeployed. */
41
+ networkId?: NetworkId.NetworkId;
42
+ /** Pre-funded accounts on the ledger-v8 chain. */
43
+ v8GenesisMints?: Arr.NonEmptyArray<V8.GenesisMint>;
44
+ /** Block producer for the ledger-v8 chain. */
45
+ v8BlockProducer?: V8.BlockProducer;
46
+ /** Block producer for the ledger-v9 chain. */
47
+ v9BlockProducer?: BlockProducer;
48
+ }>;
49
+ /**
50
+ * A simulated chain that crosses a hard fork, built from the two simulator twins.
51
+ *
52
+ * The ledger-v8 chain is available immediately and is driven like any other simulator. Once it reaches
53
+ * {@link ForkSimulatorConfig.forkBlock} the handover runs and the ledger-v9 chain appears; both remain alive for the
54
+ * simulator's scope, so assertions can read either side of the boundary.
55
+ *
56
+ * Nothing stops the ledger-v8 chain producing blocks past the boundary, which a real chain could not do — drive it only
57
+ * up to the fork.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const fork = yield* ForkSimulator.init({
62
+ * forkBlock: 3n,
63
+ * forkVersion: ProtocolVersion.ProtocolVersion(7n),
64
+ * v8GenesisMints: [{ type: 'shielded', tokenType, amount: 1_000n, recipient: v8Keys }],
65
+ * translator: translatorFromAsync(translateLedgerState),
66
+ * });
67
+ *
68
+ * yield* fork.v8.submitTransaction(v8Transfer);
69
+ * const v9 = yield* fork.advanceToFork();
70
+ * yield* v9.submitTransaction(v9Transfer);
71
+ * ```;
72
+ */
73
+ export declare class ForkSimulator {
74
+ #private;
75
+ /**
76
+ * Initialize a forking chain. The ledger-v8 chain starts immediately with the fork already scheduled; the ledger-v9
77
+ * chain is constructed when the ledger-v8 chain reaches the fork block.
78
+ *
79
+ * @param config - Configuration options
80
+ * @returns Effect that produces a ForkSimulator
81
+ */
82
+ static init(config: ForkSimulatorConfig): Effect.Effect<ForkSimulator, never, Scope.Scope>;
83
+ /** The ledger-v8 chain. */
84
+ readonly v8: V8.Simulator;
85
+ /** Height at which the chain forks. */
86
+ readonly forkBlock: bigint;
87
+ /** Protocol version the fork activates. */
88
+ readonly forkVersion: ProtocolVersion.ProtocolVersion;
89
+ private constructor();
90
+ /**
91
+ * The ledger-v9 chain, if the fork has happened.
92
+ *
93
+ * @returns `Option.some` once the handover has completed, `Option.none` before
94
+ */
95
+ v9(): Effect.Effect<Option.Option<Simulator>>;
96
+ /**
97
+ * Wait until the fork has happened, however the ledger-v8 chain is driven to it.
98
+ *
99
+ * Fails with the handover's own failure if it could not produce a ledger-v9 chain, so a broken handover surfaces here
100
+ * rather than leaving waiters blocked forever.
101
+ *
102
+ * @returns The ledger-v9 chain
103
+ */
104
+ awaitV9(): Effect.Effect<Simulator, LedgerTranslationError>;
105
+ /**
106
+ * Drive the ledger-v8 chain to the fork block with empty blocks and wait for the handover.
107
+ *
108
+ * Blocks the chain has already produced count towards the boundary, so this composes with transaction-driven
109
+ * production: submit what the test needs, then advance the rest of the way.
110
+ *
111
+ * @returns The ledger-v9 chain
112
+ */
113
+ advanceToFork(): Effect.Effect<Simulator, LedgerOps.LedgerError | LedgerTranslationError>;
114
+ }
@@ -0,0 +1,209 @@
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 simulated chain that crosses a hard fork.
15
+ *
16
+ * Ledger-v8 the chain runs on ledger-v8, ledger-v9 on ledger-v9, with real ledger bytes on both sides — enough for a
17
+ * wallet under test to observe the version change, migrate, and go on transacting.
18
+ */
19
+ import { Deferred, Effect, Option, pipe, Scope, Stream, SubscriptionRef } from 'effect';
20
+ import { LedgerState } from '@midnightntwrk/ledger-v9';
21
+ import { NetworkId, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
22
+ import { LedgerTranslationError } from './LedgerTranslation.js';
23
+ import { Simulator } from './v9/Simulator.js';
24
+ import { blankState, updateLedger } from './v9/SimulatorState.js';
25
+ import * as V8 from './v8/index.js';
26
+ // =============================================================================
27
+ // Fork Simulator
28
+ // =============================================================================
29
+ /**
30
+ * A simulated chain that crosses a hard fork, built from the two simulator twins.
31
+ *
32
+ * The ledger-v8 chain is available immediately and is driven like any other simulator. Once it reaches
33
+ * {@link ForkSimulatorConfig.forkBlock} the handover runs and the ledger-v9 chain appears; both remain alive for the
34
+ * simulator's scope, so assertions can read either side of the boundary.
35
+ *
36
+ * Nothing stops the ledger-v8 chain producing blocks past the boundary, which a real chain could not do — drive it only
37
+ * up to the fork.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const fork = yield* ForkSimulator.init({
42
+ * forkBlock: 3n,
43
+ * forkVersion: ProtocolVersion.ProtocolVersion(7n),
44
+ * v8GenesisMints: [{ type: 'shielded', tokenType, amount: 1_000n, recipient: v8Keys }],
45
+ * translator: translatorFromAsync(translateLedgerState),
46
+ * });
47
+ *
48
+ * yield* fork.v8.submitTransaction(v8Transfer);
49
+ * const v9 = yield* fork.advanceToFork();
50
+ * yield* v9.submitTransaction(v9Transfer);
51
+ * ```;
52
+ */
53
+ export class ForkSimulator {
54
+ // ===========================================================================
55
+ // Static Methods
56
+ // ===========================================================================
57
+ /**
58
+ * Initialize a forking chain. The ledger-v8 chain starts immediately with the fork already scheduled; the ledger-v9
59
+ * chain is constructed when the ledger-v8 chain reaches the fork block.
60
+ *
61
+ * @param config - Configuration options
62
+ * @returns Effect that produces a ForkSimulator
63
+ */
64
+ static init(config) {
65
+ return Effect.gen(function* () {
66
+ const networkId = config.networkId ?? NetworkId.NetworkId.Undeployed;
67
+ const v8Version = config.v8Version ?? ProtocolVersion.MinSupportedVersion;
68
+ const v8 = yield* V8.Simulator.init({
69
+ networkId,
70
+ protocolVersion: v8Version,
71
+ ...(config.v8GenesisMints !== undefined ? { genesisMints: config.v8GenesisMints } : {}),
72
+ ...(config.v8BlockProducer !== undefined ? { blockProducer: config.v8BlockProducer } : {}),
73
+ });
74
+ // Scheduled up front, so the boundary block carries the fork version however the chain is driven to it.
75
+ yield* v8.scheduleFork(config.forkBlock, config.forkVersion);
76
+ const v9Ref = yield* SubscriptionRef.make(Option.none());
77
+ const v9Ready = yield* Deferred.make();
78
+ const forkSimulator = new ForkSimulator(config, networkId, v8, v9Ref, v9Ready);
79
+ // The handover must outlive the watcher fiber, so it is built in the simulator's own scope.
80
+ const scope = yield* Effect.scope;
81
+ const runHandover = Effect.gen(function* () {
82
+ const atFork = yield* forkSimulator.#awaitForkBlock();
83
+ const v9 = yield* Scope.extend(
84
+ // Suspended so that a handover built from a throwing caller-supplied callback fails this effect rather than
85
+ // escaping as a synchronous exception.
86
+ Effect.suspend(() => forkSimulator.#handover(atFork)), scope);
87
+ // The reference is set first: anything woken by the deferred must already see the ledger-v9 chain.
88
+ yield* SubscriptionRef.set(v9Ref, Option.some(v9));
89
+ return v9;
90
+ });
91
+ // The handover runs detached, so its outcome has to be handed to whoever is waiting for the fork — including when
92
+ // it fails or dies. `intoDeferred` transfers the whole exit; completing the deferred by hand on the success path
93
+ // only would leave every waiter blocked forever on a broken handover, which reads as a hang rather than an error.
94
+ yield* Effect.forkScoped(Effect.intoDeferred(runHandover, v9Ready));
95
+ return forkSimulator;
96
+ });
97
+ }
98
+ // ===========================================================================
99
+ // Instance Properties
100
+ // ===========================================================================
101
+ #config;
102
+ #networkId;
103
+ #v9Ref;
104
+ #v9Ready;
105
+ /** The ledger-v8 chain. */
106
+ v8;
107
+ /** Height at which the chain forks. */
108
+ forkBlock;
109
+ /** Protocol version the fork activates. */
110
+ forkVersion;
111
+ constructor(config, networkId, v8, v9Ref, v9Ready) {
112
+ this.#config = config;
113
+ this.#networkId = networkId;
114
+ this.#v9Ref = v9Ref;
115
+ this.#v9Ready = v9Ready;
116
+ this.v8 = v8;
117
+ this.forkBlock = config.forkBlock;
118
+ this.forkVersion = config.forkVersion;
119
+ }
120
+ // ===========================================================================
121
+ // Instance Methods
122
+ // ===========================================================================
123
+ /**
124
+ * The ledger-v9 chain, if the fork has happened.
125
+ *
126
+ * @returns `Option.some` once the handover has completed, `Option.none` before
127
+ */
128
+ v9() {
129
+ return SubscriptionRef.get(this.#v9Ref);
130
+ }
131
+ /**
132
+ * Wait until the fork has happened, however the ledger-v8 chain is driven to it.
133
+ *
134
+ * Fails with the handover's own failure if it could not produce a ledger-v9 chain, so a broken handover surfaces here
135
+ * rather than leaving waiters blocked forever.
136
+ *
137
+ * @returns The ledger-v9 chain
138
+ */
139
+ awaitV9() {
140
+ return Deferred.await(this.#v9Ready);
141
+ }
142
+ /**
143
+ * Drive the ledger-v8 chain to the fork block with empty blocks and wait for the handover.
144
+ *
145
+ * Blocks the chain has already produced count towards the boundary, so this composes with transaction-driven
146
+ * production: submit what the test needs, then advance the rest of the way.
147
+ *
148
+ * @returns The ledger-v9 chain
149
+ */
150
+ advanceToFork() {
151
+ const v8 = this.v8;
152
+ const forkBlock = this.forkBlock;
153
+ const driveToForkBlock = () => Effect.gen(function* () {
154
+ const state = yield* v8.getLatestState();
155
+ if (V8.getCurrentBlockNumber(state) >= forkBlock)
156
+ return;
157
+ yield* v8.produceEmptyBlock();
158
+ yield* driveToForkBlock();
159
+ });
160
+ return pipe(driveToForkBlock(), Effect.flatMap(() => this.awaitV9()));
161
+ }
162
+ // ===========================================================================
163
+ // Internal
164
+ // ===========================================================================
165
+ /**
166
+ * Resolve once the ledger-v8 chain has reached the fork block, with the state at that point.
167
+ *
168
+ * The current state is checked before subscribing, and the ledger-v8 chain's shared state stream replays its latest
169
+ * value to new subscribers, so the boundary cannot slip past between the two.
170
+ */
171
+ #awaitForkBlock() {
172
+ const v8 = this.v8;
173
+ const forkBlock = this.forkBlock;
174
+ return Effect.gen(function* () {
175
+ const current = yield* v8.getLatestState();
176
+ if (V8.getCurrentBlockNumber(current) >= forkBlock)
177
+ return current;
178
+ const reached = yield* pipe(v8.state$, Stream.filter((state) => V8.getCurrentBlockNumber(state) >= forkBlock), Stream.take(1), Stream.runHead);
179
+ return yield* Option.match(reached, {
180
+ onNone: () => Effect.die(new Error('Ledger-v8 state stream ended before reaching the fork block')),
181
+ onSome: Effect.succeed,
182
+ });
183
+ });
184
+ }
185
+ /** Run the handover and construct the ledger-v9 chain, numbered and timed to continue from the fork point. */
186
+ #handover(v8State) {
187
+ const { forkVersion, forkBlock, v9BlockProducer } = this.#config;
188
+ const networkId = this.#networkId;
189
+ // The ledger-v9 chain resumes the ledger-v8 chain's height and clock, so the boundary is re-delivered rather than
190
+ // restarted. `blankState` takes the network id positionally, so it is not part of this.
191
+ const genesis = {
192
+ protocolVersion: forkVersion,
193
+ genesisBlockNumber: forkBlock,
194
+ genesisTime: v8State.currentTime,
195
+ };
196
+ /** A ledger-v9 chain whose genesis block already holds the given ledger, rather than one built by minting into it. */
197
+ const chainStartingFrom = (ledger) => pipe(Effect.promise(() => blankState(networkId, genesis)), Effect.map((state) => updateLedger(state, ledger)), Effect.flatMap((state) => Simulator.fromState(state, v9BlockProducer)));
198
+ return pipe(this.#config.translator(v8State.ledger.serialize()),
199
+ // Deserializing is the harness's job, not the translator's, so bytes ledger-v9 rejects are a
200
+ // translation failure rather than a crash out of the handover fiber.
201
+ Effect.flatMap((translated) => Effect.try({
202
+ try: () => LedgerState.deserialize(translated),
203
+ catch: (cause) => new LedgerTranslationError({
204
+ message: 'Translated bytes are not a valid ledger-v9 state',
205
+ cause,
206
+ }),
207
+ })), Effect.flatMap(chainStartingFrom));
208
+ }
209
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Translating a ledger-v8 state into a ledger-v9 one.
3
+ *
4
+ * The translation itself is a ledger-side tool reached across a WASM boundary, so this module only describes the seam,
5
+ * never the mechanism: serialized bytes in, serialized bytes out, as an `Effect`. Bytes because that is the only thing
6
+ * that crosses the boundary — neither ledger's state objects exist in the other's WASM module. An `Effect` because the
7
+ * tool has to be loaded and then run to completion (it translates incrementally, under a cost budget per step), and
8
+ * both are the translator's business rather than the caller's.
9
+ */
10
+ import { Effect } from 'effect';
11
+ declare const LedgerTranslationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
12
+ readonly _tag: "LedgerTranslationError";
13
+ } & Readonly<A>;
14
+ /** Thrown when a ledger-v8 state cannot be translated into a ledger-v9 one. */
15
+ export declare class LedgerTranslationError extends LedgerTranslationError_base<{
16
+ message: string;
17
+ cause?: unknown;
18
+ }> {
19
+ }
20
+ /**
21
+ * Translates a serialized ledger-v8 state into a serialized ledger-v9 one.
22
+ *
23
+ * Implementations own loading whatever performs the translation and running it to completion. Deserializing the result
24
+ * is the caller's, so bytes that are not valid ledger-v9 state are reported as a translation failure.
25
+ */
26
+ export type LedgerStateTranslator = (v8State: Uint8Array) => Effect.Effect<Uint8Array, LedgerTranslationError>;
27
+ /**
28
+ * Build a translator from an async function, turning anything it throws or rejects with into a
29
+ * {@link LedgerTranslationError}.
30
+ *
31
+ * This is how the WASM tool is meant to be plugged in: its call is asynchronous and it signals failure by throwing, so
32
+ * this is the whole adapter.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const translator = translatorFromAsync(async (bytes) => {
37
+ * const tool = await loadStateTranslation();
38
+ * return tool.translate(bytes);
39
+ * });
40
+ * ```;
41
+ *
42
+ * @param translate - Async translation of serialized ledger-v8 state into serialized ledger-v9 state
43
+ * @returns A translator over that function
44
+ */
45
+ export declare const translatorFromAsync: (translate: (v8State: Uint8Array) => Promise<Uint8Array>) => LedgerStateTranslator;
46
+ /**
47
+ * A translator that always fails, standing in for the real tool until it exists.
48
+ *
49
+ * Useful as an explicit default: a fork configured with it fails loudly at the boundary, rather than silently crossing
50
+ * it with state that was never translated.
51
+ */
52
+ export declare const unavailableTranslator: LedgerStateTranslator;
53
+ export {};
@@ -0,0 +1,56 @@
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
+ * Translating a ledger-v8 state into a ledger-v9 one.
15
+ *
16
+ * The translation itself is a ledger-side tool reached across a WASM boundary, so this module only describes the seam,
17
+ * never the mechanism: serialized bytes in, serialized bytes out, as an `Effect`. Bytes because that is the only thing
18
+ * that crosses the boundary — neither ledger's state objects exist in the other's WASM module. An `Effect` because the
19
+ * tool has to be loaded and then run to completion (it translates incrementally, under a cost budget per step), and
20
+ * both are the translator's business rather than the caller's.
21
+ */
22
+ import { Data, Effect } from 'effect';
23
+ /** Thrown when a ledger-v8 state cannot be translated into a ledger-v9 one. */
24
+ export class LedgerTranslationError extends Data.TaggedError('LedgerTranslationError') {
25
+ }
26
+ /**
27
+ * Build a translator from an async function, turning anything it throws or rejects with into a
28
+ * {@link LedgerTranslationError}.
29
+ *
30
+ * This is how the WASM tool is meant to be plugged in: its call is asynchronous and it signals failure by throwing, so
31
+ * this is the whole adapter.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const translator = translatorFromAsync(async (bytes) => {
36
+ * const tool = await loadStateTranslation();
37
+ * return tool.translate(bytes);
38
+ * });
39
+ * ```;
40
+ *
41
+ * @param translate - Async translation of serialized ledger-v8 state into serialized ledger-v9 state
42
+ * @returns A translator over that function
43
+ */
44
+ export const translatorFromAsync = (translate) => (v8State) => Effect.tryPromise({
45
+ try: () => translate(v8State),
46
+ catch: (cause) => new LedgerTranslationError({ message: 'Ledger state translation failed', cause }),
47
+ });
48
+ /**
49
+ * A translator that always fails, standing in for the real tool until it exists.
50
+ *
51
+ * Useful as an explicit default: a fork configured with it fails loudly at the boundary, rather than silently crossing
52
+ * it with state that was never translated.
53
+ */
54
+ export const unavailableTranslator = () => Effect.fail(new LedgerTranslationError({
55
+ message: 'A v8-to-v9 ledger state translation is not yet available; supply a translator explicitly',
56
+ }));