@midnight-ntwrk/wallet-sdk-dust-wallet 1.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 2025 Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect, ParseResult, Either, pipe, Schema } from 'effect';
14
+ import * as ledger from '@midnight-ntwrk/ledger-v6';
15
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
16
+ import { DustCoreWallet } from './DustCoreWallet.js';
17
+ const StateSchema = Schema.declare((input) => input instanceof ledger.DustLocalState).annotations({
18
+ identifier: 'ledger.DustLocalState',
19
+ });
20
+ export const Uint8ArraySchema = Schema.declare((input) => input instanceof Uint8Array).annotations({
21
+ identifier: 'Uint8Array',
22
+ });
23
+ const StateFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, StateSchema, {
24
+ encode: (state) => {
25
+ return Effect.try({
26
+ try: () => {
27
+ return state.serialize();
28
+ },
29
+ catch: (err) => {
30
+ return new ParseResult.Unexpected(err, 'Could not serialize local state');
31
+ },
32
+ });
33
+ },
34
+ decode: (bytes) => Effect.try({
35
+ try: () => ledger.DustLocalState.deserialize(bytes),
36
+ catch: (err) => {
37
+ return new ParseResult.Unexpected(err, 'Could not deserialize local state');
38
+ },
39
+ }),
40
+ }));
41
+ const HexedState = pipe(Schema.Uint8ArrayFromHex, Schema.compose(StateFromUInt8Array));
42
+ const SnapshotSchema = Schema.Struct({
43
+ publicKey: Schema.Struct({
44
+ publicKey: Schema.BigInt,
45
+ }),
46
+ state: HexedState,
47
+ protocolVersion: Schema.BigInt,
48
+ networkId: Schema.String,
49
+ offset: Schema.optional(Schema.BigInt),
50
+ });
51
+ export const makeDefaultV1SerializationCapability = () => {
52
+ return {
53
+ serialize: (wallet) => {
54
+ const buildSnapshot = (w) => ({
55
+ publicKey: w.publicKey,
56
+ state: w.state,
57
+ protocolVersion: w.protocolVersion,
58
+ networkId: w.networkId,
59
+ offset: w.progress?.appliedIndex,
60
+ });
61
+ return pipe(wallet, buildSnapshot, Schema.encodeSync(SnapshotSchema), JSON.stringify);
62
+ },
63
+ deserialize: (aux, serialized) => {
64
+ return pipe(serialized, Schema.decodeUnknownEither(Schema.parseJson(SnapshotSchema)), Either.mapLeft((err) => WalletError.WalletError.other(err)), Either.flatMap((snapshot) => Either.try({
65
+ try: () => DustCoreWallet.restore(snapshot.state, snapshot.publicKey, [], {
66
+ appliedIndex: snapshot.offset ?? 0n,
67
+ highestRelevantWalletIndex: 0n,
68
+ highestIndex: 0n,
69
+ highestRelevantIndex: 0n,
70
+ isConnected: false,
71
+ }, snapshot.protocolVersion, snapshot.networkId),
72
+ catch: (err) => WalletError.WalletError.other(err),
73
+ })));
74
+ },
75
+ };
76
+ };
@@ -0,0 +1,32 @@
1
+ import { Effect, Either, Scope, Stream, SubscriptionRef } from 'effect';
2
+ import { LedgerState, BlockContext, UserAddress, SignatureVerifyingKey, WellFormedStrictness, TransactionResult, ProofErasedTransaction, SyntheticCost } from '@midnight-ntwrk/ledger-v6';
3
+ import { LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
4
+ import { NetworkId } from './types/ledger.js';
5
+ export type SimulatorState = Readonly<{
6
+ networkId: NetworkId;
7
+ ledger: LedgerState;
8
+ lastTx: ProofErasedTransaction | undefined;
9
+ lastTxResult: TransactionResult | undefined;
10
+ lastTxNumber: bigint;
11
+ }>;
12
+ export declare class Simulator {
13
+ #private;
14
+ static nextBlockContext: (blockTime: Date) => Effect.Effect<BlockContext>;
15
+ static init(networkId: NetworkId): Effect.Effect<Simulator, never, Scope.Scope>;
16
+ static apply(simulatorState: SimulatorState, tx: ProofErasedTransaction, strictness: WellFormedStrictness, blockContext: BlockContext, blockFullness?: SyntheticCost): Either.Either<[{
17
+ blockNumber: bigint;
18
+ blockHash: string;
19
+ }, SimulatorState], LedgerOps.LedgerError>;
20
+ readonly state$: Stream.Stream<SimulatorState>;
21
+ constructor(stateRef: SubscriptionRef.SubscriptionRef<SimulatorState>, state$: Stream.Stream<SimulatorState>);
22
+ getLatestState(): Effect.Effect<SimulatorState>;
23
+ rewardNight(recipient: UserAddress, amount: bigint, verifyingKey: SignatureVerifyingKey): Effect.Effect<{
24
+ blockNumber: bigint;
25
+ blockHash: string;
26
+ }, LedgerOps.LedgerError>;
27
+ submitRegularTx(tx: ProofErasedTransaction, blockFullness?: SyntheticCost): Effect.Effect<{
28
+ blockNumber: bigint;
29
+ blockHash: string;
30
+ }, LedgerOps.LedgerError>;
31
+ fastForward(lastTxNumber: bigint): Effect.Effect<undefined, LedgerOps.LedgerError>;
32
+ }
@@ -0,0 +1,110 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 2025 Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect, Encoding, pipe, Stream, SubscriptionRef } from 'effect';
14
+ import { LedgerState, ClaimRewardsTransaction, SignatureErased, Transaction, WellFormedStrictness, TransactionContext, } from '@midnight-ntwrk/ledger-v6';
15
+ import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
16
+ import * as crypto from 'crypto';
17
+ const simpleHash = (input) => {
18
+ return Encoding.decodeHex(input).pipe(EitherOps.toEffect, Effect.andThen((parsed) => Effect.promise(() => crypto.subtle.digest('SHA-256', parsed))), Effect.andThen((out) => Encoding.encodeHex(new Uint8Array(out))), Effect.orDie);
19
+ };
20
+ export class Simulator {
21
+ static nextBlockContext = (blockTime) => pipe(DateOps.dateToSeconds(blockTime).toString(16), (str) => (str.length % 2 == 0 ? str : str.padStart(str.length + 1, '0')), simpleHash, Effect.map((hash) => ({
22
+ parentBlockHash: hash,
23
+ secondsSinceEpoch: DateOps.dateToSeconds(blockTime),
24
+ secondsSinceEpochErr: 1,
25
+ })));
26
+ static init(networkId) {
27
+ return Effect.gen(function* () {
28
+ const initialState = {
29
+ networkId,
30
+ ledger: LedgerState.blank(networkId),
31
+ lastTx: undefined,
32
+ lastTxResult: undefined,
33
+ lastTxNumber: 0n,
34
+ };
35
+ const ref = yield* SubscriptionRef.make(initialState);
36
+ const changesStream = yield* Stream.share(ref.changes, {
37
+ capacity: 'unbounded',
38
+ replay: Number.MAX_SAFE_INTEGER,
39
+ });
40
+ yield* pipe(changesStream, Stream.runDrain, Effect.forkScoped);
41
+ return new Simulator(ref, changesStream);
42
+ });
43
+ }
44
+ static apply(simulatorState, tx, strictness, blockContext, blockFullness) {
45
+ return LedgerOps.ledgerTry(() => {
46
+ blockFullness = blockFullness ?? tx.cost(simulatorState.ledger.parameters);
47
+ const blockNumber = blockContext.secondsSinceEpoch;
48
+ const blockTime = DateOps.secondsToDate(blockNumber);
49
+ const verifiedTransaction = tx.wellFormed(simulatorState.ledger, strictness, blockTime);
50
+ const transactionContext = new TransactionContext(simulatorState.ledger, blockContext);
51
+ const [newLedgerState, txResult] = simulatorState.ledger.apply(verifiedTransaction, transactionContext);
52
+ const newSimulatorState = {
53
+ ...simulatorState,
54
+ ledger: newLedgerState.postBlockUpdate(blockTime, blockFullness),
55
+ lastTx: tx,
56
+ lastTxResult: txResult,
57
+ lastTxNumber: blockNumber,
58
+ };
59
+ const output = {
60
+ blockNumber,
61
+ blockHash: blockContext.parentBlockHash,
62
+ };
63
+ return [output, newSimulatorState];
64
+ });
65
+ }
66
+ #stateRef;
67
+ state$;
68
+ constructor(stateRef, state$) {
69
+ this.#stateRef = stateRef;
70
+ this.state$ = state$;
71
+ }
72
+ getLatestState() {
73
+ return SubscriptionRef.get(this.#stateRef);
74
+ }
75
+ rewardNight(recipient, amount, verifyingKey) {
76
+ return SubscriptionRef.modifyEffect(this.#stateRef, (simulatorState) => Effect.gen(function* () {
77
+ const nextNumber = DateOps.secondsToDate(simulatorState.lastTxNumber + 1n);
78
+ const newLedgerState = yield* LedgerOps.ledgerTry(() => simulatorState.ledger.testingDistributeNight(recipient, amount, nextNumber));
79
+ const newSimulatorState = {
80
+ ...simulatorState,
81
+ ledger: newLedgerState,
82
+ };
83
+ const signature = new SignatureErased();
84
+ const claimRewardsTransaction = new ClaimRewardsTransaction(signature.instance, newSimulatorState.networkId, amount, verifyingKey, LedgerOps.randomNonce(), signature);
85
+ const tx = Transaction.fromRewards(claimRewardsTransaction).eraseProofs();
86
+ const blockContext = yield* Simulator.nextBlockContext(nextNumber);
87
+ return yield* Simulator.apply(newSimulatorState, tx, new WellFormedStrictness(), blockContext);
88
+ }));
89
+ }
90
+ submitRegularTx(tx, blockFullness) {
91
+ return SubscriptionRef.modifyEffect(this.#stateRef, (simulatorState) => Effect.gen(function* () {
92
+ const nextNumber = DateOps.secondsToDate(simulatorState.lastTxNumber + 1n);
93
+ const context = yield* Simulator.nextBlockContext(nextNumber);
94
+ return yield* Simulator.apply(simulatorState, tx, new WellFormedStrictness(), context, blockFullness);
95
+ }));
96
+ }
97
+ fastForward(lastTxNumber) {
98
+ return SubscriptionRef.modify(this.#stateRef, (simulatorState) => {
99
+ return [
100
+ undefined,
101
+ {
102
+ ...simulatorState,
103
+ lastTxNumber,
104
+ lastTx: undefined,
105
+ lastTxResult: undefined,
106
+ },
107
+ ];
108
+ });
109
+ }
110
+ }
@@ -0,0 +1,33 @@
1
+ import { Effect } from 'effect';
2
+ import { SubmissionEvent as SubmissionEventImported } from '@midnight-ntwrk/wallet-sdk-node-client/effect';
3
+ import { FinalizedTransaction, ProofErasedTransaction } from '@midnight-ntwrk/ledger-v6';
4
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
+ import { Simulator } from './Simulator.js';
6
+ import { NetworkId } from './types/ledger.js';
7
+ export declare const SubmissionEvent: typeof SubmissionEventImported;
8
+ export type SubmissionEvent = SubmissionEventImported.SubmissionEvent;
9
+ export declare namespace SubmissionEventCases {
10
+ type Finalized = SubmissionEventImported.Cases.Finalized;
11
+ type Submitted = SubmissionEventImported.Cases.Submitted;
12
+ type InBlock = SubmissionEventImported.Cases.InBlock;
13
+ }
14
+ export type SubmitTransactionMethod<TTransaction> = {
15
+ (transaction: TTransaction, waitForStatus: 'Submitted'): Effect.Effect<SubmissionEventCases.Submitted, WalletError.WalletError>;
16
+ (transaction: TTransaction, waitForStatus: 'InBlock'): Effect.Effect<SubmissionEventCases.InBlock, WalletError.WalletError>;
17
+ (transaction: TTransaction, waitForStatus: 'Finalized'): Effect.Effect<SubmissionEventCases.Finalized, WalletError.WalletError>;
18
+ (transaction: TTransaction): Effect.Effect<SubmissionEventCases.InBlock, WalletError.WalletError>;
19
+ (transaction: TTransaction, waitForStatus?: 'Submitted' | 'InBlock' | 'Finalized'): Effect.Effect<SubmissionEvent, WalletError.WalletError>;
20
+ };
21
+ export interface SubmissionService<TTransaction> {
22
+ submitTransaction: SubmitTransactionMethod<TTransaction>;
23
+ close(): Effect.Effect<void>;
24
+ }
25
+ export type DefaultSubmissionConfiguration = {
26
+ relayURL: URL;
27
+ networkId: NetworkId;
28
+ };
29
+ export declare const makeDefaultSubmissionService: (config: DefaultSubmissionConfiguration) => SubmissionService<FinalizedTransaction>;
30
+ export type SimulatorSubmissionConfiguration = {
31
+ simulator: Simulator;
32
+ };
33
+ export declare const makeSimulatorSubmissionService: (waitForStatus?: "Submitted" | "InBlock" | "Finalized") => (config: SimulatorSubmissionConfiguration) => SubmissionService<ProofErasedTransaction>;
@@ -0,0 +1,69 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 2025 Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Deferred, Effect, Encoding, Exit, pipe, Scope } from 'effect';
14
+ import { NodeClient, PolkadotNodeClient, SubmissionEvent as SubmissionEventImported, } from '@midnight-ntwrk/wallet-sdk-node-client/effect';
15
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
16
+ export const SubmissionEvent = SubmissionEventImported;
17
+ export const makeDefaultSubmissionService = (config) => {
18
+ const scopeAndClientDeferred = Deferred.make().pipe(Effect.runSync);
19
+ const makeScopeAndClient = Effect.gen(function* () {
20
+ const scope = yield* Scope.make();
21
+ const client = yield* PolkadotNodeClient.make({
22
+ nodeURL: config.relayURL,
23
+ }).pipe(Effect.provideService(Scope.Scope, scope));
24
+ return { scope, client };
25
+ });
26
+ void pipe(scopeAndClientDeferred, Deferred.complete(makeScopeAndClient), Effect.runPromise);
27
+ const submit = (transaction, waitForStatus = 'InBlock') => {
28
+ return pipe(NodeClient.sendMidnightTransactionAndWait(transaction.serialize(), waitForStatus), Effect.provideServiceEffect(NodeClient.NodeClient, pipe(scopeAndClientDeferred, Deferred.await, Effect.map(({ client }) => client))), Effect.mapError((err) => WalletError.WalletError.submission(err)));
29
+ };
30
+ return {
31
+ submitTransaction: submit,
32
+ close() {
33
+ return pipe(scopeAndClientDeferred, Deferred.await, Effect.flatMap(({ scope }) => Scope.close(scope, Exit.void)), Effect.ignoreLogged);
34
+ },
35
+ };
36
+ };
37
+ export const makeSimulatorSubmissionService = (waitForStatus = 'InBlock') => (config) => {
38
+ const submit = (transaction) => {
39
+ const serializedTx = transaction.serialize();
40
+ return config.simulator.submitRegularTx(transaction).pipe(Effect.map((output) => {
41
+ // Let's mimic node's client behavior here
42
+ switch (waitForStatus) {
43
+ case 'Submitted':
44
+ return SubmissionEvent.Submitted({
45
+ tx: serializedTx,
46
+ txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
47
+ });
48
+ case 'InBlock':
49
+ return SubmissionEvent.InBlock({
50
+ tx: serializedTx,
51
+ blockHash: output.blockHash,
52
+ blockHeight: output.blockNumber,
53
+ txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
54
+ });
55
+ case 'Finalized':
56
+ return SubmissionEvent.Finalized({
57
+ tx: serializedTx,
58
+ blockHash: output.blockHash,
59
+ blockHeight: output.blockNumber,
60
+ txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
61
+ });
62
+ }
63
+ }));
64
+ };
65
+ return {
66
+ submitTransaction: submit,
67
+ close: () => Effect.void,
68
+ };
69
+ };
package/dist/Sync.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { Effect, Layer, Schema, Scope, Stream } from 'effect';
2
+ import { DustSecretKey, Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
3
+ import { SubscriptionClient, QueryClient } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
4
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
+ import { Simulator, SimulatorState } from './Simulator.js';
6
+ import { DustCoreWallet } from './DustCoreWallet.js';
7
+ import { NetworkId } from './types/ledger.js';
8
+ export interface SyncService<TState, TStartAux, TUpdate> {
9
+ updates: (state: TState, auxData: TStartAux) => Stream.Stream<TUpdate, WalletError.WalletError, Scope.Scope>;
10
+ ledgerParameters: () => Effect.Effect<LedgerParameters, WalletError.WalletError>;
11
+ }
12
+ export interface SyncCapability<TState, TUpdate> {
13
+ applyUpdate: (state: TState, update: TUpdate) => TState;
14
+ }
15
+ export type IndexerClientConnection = {
16
+ indexerHttpUrl: string;
17
+ indexerWsUrl?: string;
18
+ };
19
+ export type DefaultSyncConfiguration = {
20
+ indexerClientConnection: IndexerClientConnection;
21
+ networkId: NetworkId;
22
+ };
23
+ export type SimulatorSyncConfiguration = {
24
+ simulator: Simulator;
25
+ networkId: NetworkId;
26
+ };
27
+ export type SimulatorSyncUpdate = {
28
+ update: SimulatorState;
29
+ secretKey: DustSecretKey;
30
+ };
31
+ type SecretKeysResource = <A>(cb: (key: DustSecretKey) => A) => A;
32
+ export declare const SecretKeysResource: {
33
+ create: (secretKey: DustSecretKey) => SecretKeysResource;
34
+ };
35
+ export declare const SyncEventsUpdateSchema: Schema.Struct<{
36
+ id: typeof Schema.Number;
37
+ raw: Schema.Schema<LedgerEvent, string, never>;
38
+ maxId: typeof Schema.Number;
39
+ }>;
40
+ export type WalletSyncSubscription = Schema.Schema.Type<typeof SyncEventsUpdateSchema>;
41
+ export type WalletSyncUpdate = {
42
+ update: WalletSyncSubscription;
43
+ secretKeys: SecretKeysResource;
44
+ };
45
+ export declare const WalletSyncUpdate: {
46
+ create: (update: WalletSyncSubscription, secretKey: DustSecretKey) => WalletSyncUpdate;
47
+ };
48
+ export declare const makeDefaultSyncService: (config: DefaultSyncConfiguration) => SyncService<DustCoreWallet, DustSecretKey, WalletSyncUpdate>;
49
+ export type IndexerSyncService = {
50
+ connectionLayer: () => Layer.Layer<SubscriptionClient, WalletError.WalletError, Scope.Scope>;
51
+ subscribeWallet: (state: DustCoreWallet) => Stream.Stream<WalletSyncSubscription, WalletError.WalletError, Scope.Scope | SubscriptionClient>;
52
+ queryClient: () => Layer.Layer<QueryClient, WalletError.WalletError, Scope.Scope>;
53
+ };
54
+ export declare const makeIndexerSyncService: (config: DefaultSyncConfiguration) => IndexerSyncService;
55
+ export declare const makeDefaultSyncCapability: () => SyncCapability<DustCoreWallet, WalletSyncUpdate>;
56
+ export declare const makeSimulatorSyncService: (config: SimulatorSyncConfiguration) => SyncService<DustCoreWallet, DustSecretKey, SimulatorSyncUpdate>;
57
+ export declare const makeSimulatorSyncCapability: () => SyncCapability<DustCoreWallet, SimulatorSyncUpdate>;
58
+ export {};
package/dist/Sync.js ADDED
@@ -0,0 +1,135 @@
1
+ // This file is part of MIDNIGHT-WALLET-SDK.
2
+ // Copyright (C) 2025 Midnight Foundation
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // You may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { Effect, Either, Layer, ParseResult, pipe, Schema, Stream } from 'effect';
14
+ import { Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
15
+ import { BlockHash, DustLedgerEvents } from '@midnight-ntwrk/wallet-sdk-indexer-client';
16
+ import { WsSubscriptionClient, HttpQueryClient, ConnectionHelper, } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
17
+ import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
18
+ import { WsURL } from '@midnight-ntwrk/wallet-sdk-utilities/networking';
19
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
20
+ import { Uint8ArraySchema } from './Serialization.js';
21
+ export const SecretKeysResource = {
22
+ create: (secretKey) => {
23
+ let sk = secretKey;
24
+ return (cb) => {
25
+ if (sk === null || sk === undefined) {
26
+ throw new Error('Secret key has been consumed');
27
+ }
28
+ const result = cb(sk);
29
+ sk = null;
30
+ return result;
31
+ };
32
+ },
33
+ };
34
+ const LedgerEventSchema = Schema.declare((input) => input instanceof LedgerEvent).annotations({
35
+ identifier: 'ledger.Event',
36
+ });
37
+ const LedgerEventFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, LedgerEventSchema, {
38
+ encode: (e) => {
39
+ return Effect.try({
40
+ try: () => e.serialize(),
41
+ catch: (err) => {
42
+ return new ParseResult.Unexpected(err, 'Could not serialize Ledger Event');
43
+ },
44
+ });
45
+ },
46
+ decode: (bytes) => Effect.try({
47
+ try: () => LedgerEvent.deserialize(bytes),
48
+ catch: (err) => {
49
+ return new ParseResult.Unexpected(err, 'Could not deserialize Ledger Event');
50
+ },
51
+ }),
52
+ }));
53
+ const HexedEvent = pipe(Schema.Uint8ArrayFromHex, Schema.compose(LedgerEventFromUInt8Array));
54
+ export const SyncEventsUpdateSchema = Schema.Struct({
55
+ id: Schema.Number,
56
+ raw: HexedEvent,
57
+ maxId: Schema.Number,
58
+ });
59
+ export const WalletSyncUpdate = {
60
+ create: (update, secretKey) => {
61
+ return {
62
+ update,
63
+ secretKeys: SecretKeysResource.create(secretKey),
64
+ };
65
+ },
66
+ };
67
+ export const makeDefaultSyncService = (config) => {
68
+ const indexerSyncService = makeIndexerSyncService(config);
69
+ return {
70
+ updates: (state, secretKey) => {
71
+ return pipe(indexerSyncService.subscribeWallet(state), Stream.map((data) => WalletSyncUpdate.create(data, secretKey)), Stream.provideSomeLayer(indexerSyncService.connectionLayer()));
72
+ },
73
+ ledgerParameters: () => {
74
+ return Effect.gen(function* () {
75
+ const query = yield* BlockHash;
76
+ const result = yield* query({ offset: null });
77
+ return result.block?.ledgerParameters;
78
+ }).pipe(Effect.provide(indexerSyncService.queryClient()), Effect.scoped, Effect.catchAll((err) => Effect.fail(WalletError.WalletError.other(`Encountered unexpected error: ${err.message}`))), Effect.flatMap((ledgerParameters) => {
79
+ if (ledgerParameters === undefined) {
80
+ return Effect.fail(WalletError.WalletError.other('Unable to fetch ledger parameters'));
81
+ }
82
+ return LedgerOps.ledgerTry(() => LedgerParameters.deserialize(Buffer.from(ledgerParameters, 'hex')));
83
+ }));
84
+ },
85
+ };
86
+ };
87
+ export const makeIndexerSyncService = (config) => {
88
+ return {
89
+ queryClient() {
90
+ return pipe(HttpQueryClient.layer({ url: config.indexerClientConnection.indexerHttpUrl }), Layer.mapError((error) => WalletError.WalletError.other(error)));
91
+ },
92
+ connectionLayer() {
93
+ const { indexerClientConnection } = config;
94
+ return ConnectionHelper.createWebSocketUrl(indexerClientConnection.indexerHttpUrl, indexerClientConnection.indexerWsUrl).pipe(Either.flatMap((url) => WsURL.make(url)), Either.match({
95
+ onLeft: (error) => Layer.fail(error),
96
+ onRight: (url) => WsSubscriptionClient.layer({ url }),
97
+ }), Layer.mapError((e) => new WalletError.SyncWalletError({ message: 'Failed to to obtain correct indexer URLs', cause: e })));
98
+ },
99
+ subscribeWallet(state) {
100
+ const { appliedIndex } = state.progress;
101
+ return pipe(DustLedgerEvents.run({
102
+ id: Number(appliedIndex),
103
+ }), Stream.mapEffect((subscription) => pipe(Schema.decodeUnknownEither(SyncEventsUpdateSchema)(subscription.dustLedgerEvents), Either.mapLeft((err) => new WalletError.SyncWalletError(err)), EitherOps.toEffect)), Stream.mapError((error) => new WalletError.SyncWalletError(error)));
104
+ },
105
+ };
106
+ };
107
+ export const makeDefaultSyncCapability = () => {
108
+ return {
109
+ applyUpdate(state, wrappedUpdate) {
110
+ const { update, secretKeys } = wrappedUpdate;
111
+ const nextIndex = BigInt(update.id);
112
+ const highestRelevantWalletIndex = BigInt(update.maxId);
113
+ // in case the nextIndex is less than or equal to the current appliedIndex
114
+ // just update highestRelevantWalletIndex
115
+ if (nextIndex <= state.progress.appliedIndex) {
116
+ return state.updateProgress({ highestRelevantWalletIndex, isConnected: true });
117
+ }
118
+ const events = [update.raw].filter((event) => event !== null);
119
+ return secretKeys((keys) => state
120
+ .applyEvents(keys, events, new Date())
121
+ .updateProgress({ appliedIndex: nextIndex, highestRelevantWalletIndex, isConnected: true }));
122
+ },
123
+ };
124
+ };
125
+ export const makeSimulatorSyncService = (config) => {
126
+ return {
127
+ updates: (_state, secretKey) => config.simulator.state$.pipe(Stream.map((state) => ({ update: state, secretKey }))),
128
+ ledgerParameters: () => pipe(config.simulator.getLatestState(), Effect.map((state) => state.ledger.parameters)),
129
+ };
130
+ };
131
+ export const makeSimulatorSyncCapability = () => ({
132
+ applyUpdate: (state, update) => state
133
+ .applyEvents(update.secretKey, update.update.lastTxResult?.events || [], DateOps.secondsToDate(update.update.lastTxNumber))
134
+ .updateProgress({ appliedIndex: update.update.lastTxNumber }),
135
+ });
@@ -0,0 +1,50 @@
1
+ import { Either } from 'effect';
2
+ import { DustSecretKey, Signature, SignatureVerifyingKey, Utxo, FinalizedTransaction, ProofErasedTransaction, UnprovenTransaction, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
3
+ import { ProvingRecipe, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { DustCoreWallet } from './DustCoreWallet.js';
5
+ import { AnyTransaction, DustToken, NetworkId, TotalCostParameters } from './types/index.js';
6
+ import { CoinsAndBalancesCapability, CoinSelection, CoinWithValue } from './CoinsAndBalances.js';
7
+ import { KeysCapability } from './Keys.js';
8
+ export interface TransactingCapability<TSecrets, TState, TTransaction> {
9
+ readonly networkId: NetworkId;
10
+ readonly costParams: TotalCostParameters;
11
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<CoinWithValue<Utxo>>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
12
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Either.Either<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
13
+ calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
14
+ addFeePayment(secretKey: TSecrets, state: TState, transaction: UnprovenTransaction, currentTime: Date, ttl: Date, ledgerParams: LedgerParameters): Either.Either<{
15
+ recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>;
16
+ newState: TState;
17
+ }, WalletError.WalletError>;
18
+ revert(state: TState, tx: TTransaction): Either.Either<TState, WalletError.WalletError>;
19
+ revertRecipe(state: TState, recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Either.Either<TState, WalletError.WalletError>;
20
+ }
21
+ export type DefaultTransactingConfiguration = {
22
+ networkId: NetworkId;
23
+ costParameters: TotalCostParameters;
24
+ };
25
+ export type DefaultTransactingContext = {
26
+ coinSelection: CoinSelection<DustToken>;
27
+ coinsAndBalancesCapability: CoinsAndBalancesCapability<DustCoreWallet>;
28
+ keysCapability: KeysCapability<DustCoreWallet>;
29
+ };
30
+ export declare const makeDefaultTransactingCapability: (config: DefaultTransactingConfiguration, getContext: () => DefaultTransactingContext) => TransactingCapability<DustSecretKey, DustCoreWallet, FinalizedTransaction>;
31
+ export declare const makeSimulatorTransactingCapability: (config: DefaultTransactingConfiguration, getContext: () => DefaultTransactingContext) => TransactingCapability<DustSecretKey, DustCoreWallet, ProofErasedTransaction>;
32
+ export declare class TransactingCapabilityImplementation<TTransaction extends AnyTransaction> implements TransactingCapability<DustSecretKey, DustCoreWallet, TTransaction> {
33
+ #private;
34
+ readonly networkId: string;
35
+ readonly costParams: TotalCostParameters;
36
+ readonly getCoinSelection: () => CoinSelection<DustToken>;
37
+ readonly getCoins: () => CoinsAndBalancesCapability<DustCoreWallet>;
38
+ readonly getKeys: () => KeysCapability<DustCoreWallet>;
39
+ constructor(networkId: NetworkId, costParams: TotalCostParameters, getCoinSelection: () => CoinSelection<DustToken>, getCoins: () => CoinsAndBalancesCapability<DustCoreWallet>, getKeys: () => KeysCapability<DustCoreWallet>);
40
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<CoinWithValue<Utxo>>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Either.Either<UnprovenTransaction, WalletError.WalletError>;
41
+ addDustGenerationSignature(transaction: UnprovenTransaction, signatureData: Signature): Either.Either<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
42
+ calculateFee(transaction: AnyTransaction, ledgerParams: LedgerParameters): bigint;
43
+ static feeImbalance(transaction: AnyTransaction, totalFee: bigint): bigint;
44
+ addFeePayment(secretKey: DustSecretKey, state: DustCoreWallet, transaction: UnprovenTransaction, currentTime: Date, ttl: Date, ledgerParams: LedgerParameters): Either.Either<{
45
+ recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>;
46
+ newState: DustCoreWallet;
47
+ }, WalletError.WalletError>;
48
+ revert(state: DustCoreWallet, tx: TTransaction): Either.Either<DustCoreWallet, WalletError.WalletError>;
49
+ revertRecipe(state: DustCoreWallet, recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Either.Either<DustCoreWallet, WalletError.WalletError>;
50
+ }