@midnight-ntwrk/wallet-sdk-dust-wallet 1.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/CoinsAndBalances.d.ts +19 -0
- package/dist/CoinsAndBalances.js +78 -0
- package/dist/DustCoreWallet.d.ts +31 -0
- package/dist/DustCoreWallet.js +96 -0
- package/dist/DustWallet.d.ts +51 -0
- package/dist/DustWallet.js +128 -0
- package/dist/Keys.d.ts +8 -0
- package/dist/Keys.js +11 -0
- package/dist/RunningV1Variant.d.ts +44 -0
- package/dist/RunningV1Variant.js +113 -0
- package/dist/Serialization.d.ts +9 -0
- package/dist/Serialization.js +64 -0
- package/dist/Simulator.d.ts +32 -0
- package/dist/Simulator.js +98 -0
- package/dist/Submission.d.ts +33 -0
- package/dist/Submission.js +57 -0
- package/dist/Sync.d.ts +58 -0
- package/dist/Sync.js +123 -0
- package/dist/Transacting.d.ts +50 -0
- package/dist/Transacting.js +183 -0
- package/dist/Utils.d.ts +14 -0
- package/dist/Utils.js +14 -0
- package/dist/V1Builder.d.ts +100 -0
- package/dist/V1Builder.js +169 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/types/Dust.d.ts +30 -0
- package/dist/types/Dust.js +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/ledger.d.ts +4 -0
- package/dist/types/ledger.js +1 -0
- package/dist/types/transaction.d.ts +4 -0
- package/dist/types/transaction.js +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Effect, ParseResult, Either, pipe, Schema } from 'effect';
|
|
2
|
+
import * as ledger from '@midnight-ntwrk/ledger-v6';
|
|
3
|
+
import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
|
|
4
|
+
import { DustCoreWallet } from './DustCoreWallet.js';
|
|
5
|
+
const StateSchema = Schema.declare((input) => input instanceof ledger.DustLocalState).annotations({
|
|
6
|
+
identifier: 'ledger.DustLocalState',
|
|
7
|
+
});
|
|
8
|
+
export const Uint8ArraySchema = Schema.declare((input) => input instanceof Uint8Array).annotations({
|
|
9
|
+
identifier: 'Uint8Array',
|
|
10
|
+
});
|
|
11
|
+
const StateFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, StateSchema, {
|
|
12
|
+
encode: (state) => {
|
|
13
|
+
return Effect.try({
|
|
14
|
+
try: () => {
|
|
15
|
+
return state.serialize();
|
|
16
|
+
},
|
|
17
|
+
catch: (err) => {
|
|
18
|
+
return new ParseResult.Unexpected(err, 'Could not serialize local state');
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
decode: (bytes) => Effect.try({
|
|
23
|
+
try: () => ledger.DustLocalState.deserialize(bytes),
|
|
24
|
+
catch: (err) => {
|
|
25
|
+
return new ParseResult.Unexpected(err, 'Could not deserialize local state');
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
}));
|
|
29
|
+
const HexedState = pipe(Schema.Uint8ArrayFromHex, Schema.compose(StateFromUInt8Array));
|
|
30
|
+
const SnapshotSchema = Schema.Struct({
|
|
31
|
+
publicKey: Schema.Struct({
|
|
32
|
+
publicKey: Schema.BigInt,
|
|
33
|
+
}),
|
|
34
|
+
state: HexedState,
|
|
35
|
+
protocolVersion: Schema.BigInt,
|
|
36
|
+
networkId: Schema.String,
|
|
37
|
+
offset: Schema.optional(Schema.BigInt),
|
|
38
|
+
});
|
|
39
|
+
export const makeDefaultV1SerializationCapability = () => {
|
|
40
|
+
return {
|
|
41
|
+
serialize: (wallet) => {
|
|
42
|
+
const buildSnapshot = (w) => ({
|
|
43
|
+
publicKey: w.publicKey,
|
|
44
|
+
state: w.state,
|
|
45
|
+
protocolVersion: w.protocolVersion,
|
|
46
|
+
networkId: w.networkId,
|
|
47
|
+
offset: w.progress?.appliedIndex,
|
|
48
|
+
});
|
|
49
|
+
return pipe(wallet, buildSnapshot, Schema.encodeSync(SnapshotSchema), JSON.stringify);
|
|
50
|
+
},
|
|
51
|
+
deserialize: (aux, serialized) => {
|
|
52
|
+
return pipe(serialized, Schema.decodeUnknownEither(Schema.parseJson(SnapshotSchema)), Either.mapLeft((err) => WalletError.WalletError.other(err)), Either.flatMap((snapshot) => Either.try({
|
|
53
|
+
try: () => DustCoreWallet.restore(snapshot.state, snapshot.publicKey, [], {
|
|
54
|
+
appliedIndex: snapshot.offset ?? 0n,
|
|
55
|
+
highestRelevantWalletIndex: 0n,
|
|
56
|
+
highestIndex: 0n,
|
|
57
|
+
highestRelevantIndex: 0n,
|
|
58
|
+
isConnected: false,
|
|
59
|
+
}, snapshot.protocolVersion, snapshot.networkId),
|
|
60
|
+
catch: (err) => WalletError.WalletError.other(err),
|
|
61
|
+
})));
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -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,98 @@
|
|
|
1
|
+
import { Effect, Encoding, pipe, Stream, SubscriptionRef } from 'effect';
|
|
2
|
+
import { LedgerState, ClaimRewardsTransaction, SignatureErased, Transaction, WellFormedStrictness, TransactionContext, } from '@midnight-ntwrk/ledger-v6';
|
|
3
|
+
import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
const simpleHash = (input) => {
|
|
6
|
+
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);
|
|
7
|
+
};
|
|
8
|
+
export class Simulator {
|
|
9
|
+
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) => ({
|
|
10
|
+
parentBlockHash: hash,
|
|
11
|
+
secondsSinceEpoch: DateOps.dateToSeconds(blockTime),
|
|
12
|
+
secondsSinceEpochErr: 1,
|
|
13
|
+
})));
|
|
14
|
+
static init(networkId) {
|
|
15
|
+
return Effect.gen(function* () {
|
|
16
|
+
const initialState = {
|
|
17
|
+
networkId,
|
|
18
|
+
ledger: LedgerState.blank(networkId),
|
|
19
|
+
lastTx: undefined,
|
|
20
|
+
lastTxResult: undefined,
|
|
21
|
+
lastTxNumber: 0n,
|
|
22
|
+
};
|
|
23
|
+
const ref = yield* SubscriptionRef.make(initialState);
|
|
24
|
+
const changesStream = yield* Stream.share(ref.changes, {
|
|
25
|
+
capacity: 'unbounded',
|
|
26
|
+
replay: Number.MAX_SAFE_INTEGER,
|
|
27
|
+
});
|
|
28
|
+
yield* pipe(changesStream, Stream.runDrain, Effect.forkScoped);
|
|
29
|
+
return new Simulator(ref, changesStream);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
static apply(simulatorState, tx, strictness, blockContext, blockFullness) {
|
|
33
|
+
return LedgerOps.ledgerTry(() => {
|
|
34
|
+
blockFullness = blockFullness ?? tx.cost(simulatorState.ledger.parameters);
|
|
35
|
+
const blockNumber = blockContext.secondsSinceEpoch;
|
|
36
|
+
const blockTime = DateOps.secondsToDate(blockNumber);
|
|
37
|
+
const verifiedTransaction = tx.wellFormed(simulatorState.ledger, strictness, blockTime);
|
|
38
|
+
const transactionContext = new TransactionContext(simulatorState.ledger, blockContext);
|
|
39
|
+
const [newLedgerState, txResult] = simulatorState.ledger.apply(verifiedTransaction, transactionContext);
|
|
40
|
+
const newSimulatorState = {
|
|
41
|
+
...simulatorState,
|
|
42
|
+
ledger: newLedgerState.postBlockUpdate(blockTime, blockFullness),
|
|
43
|
+
lastTx: tx,
|
|
44
|
+
lastTxResult: txResult,
|
|
45
|
+
lastTxNumber: blockNumber,
|
|
46
|
+
};
|
|
47
|
+
const output = {
|
|
48
|
+
blockNumber,
|
|
49
|
+
blockHash: blockContext.parentBlockHash,
|
|
50
|
+
};
|
|
51
|
+
return [output, newSimulatorState];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
#stateRef;
|
|
55
|
+
state$;
|
|
56
|
+
constructor(stateRef, state$) {
|
|
57
|
+
this.#stateRef = stateRef;
|
|
58
|
+
this.state$ = state$;
|
|
59
|
+
}
|
|
60
|
+
getLatestState() {
|
|
61
|
+
return SubscriptionRef.get(this.#stateRef);
|
|
62
|
+
}
|
|
63
|
+
rewardNight(recipient, amount, verifyingKey) {
|
|
64
|
+
return SubscriptionRef.modifyEffect(this.#stateRef, (simulatorState) => Effect.gen(function* () {
|
|
65
|
+
const nextNumber = DateOps.secondsToDate(simulatorState.lastTxNumber + 1n);
|
|
66
|
+
const newLedgerState = yield* LedgerOps.ledgerTry(() => simulatorState.ledger.testingDistributeNight(recipient, amount, nextNumber));
|
|
67
|
+
const newSimulatorState = {
|
|
68
|
+
...simulatorState,
|
|
69
|
+
ledger: newLedgerState,
|
|
70
|
+
};
|
|
71
|
+
const signature = new SignatureErased();
|
|
72
|
+
const claimRewardsTransaction = new ClaimRewardsTransaction(signature.instance, newSimulatorState.networkId, amount, verifyingKey, LedgerOps.randomNonce(), signature);
|
|
73
|
+
const tx = Transaction.fromRewards(claimRewardsTransaction).eraseProofs();
|
|
74
|
+
const blockContext = yield* Simulator.nextBlockContext(nextNumber);
|
|
75
|
+
return yield* Simulator.apply(newSimulatorState, tx, new WellFormedStrictness(), blockContext);
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
submitRegularTx(tx, blockFullness) {
|
|
79
|
+
return SubscriptionRef.modifyEffect(this.#stateRef, (simulatorState) => Effect.gen(function* () {
|
|
80
|
+
const nextNumber = DateOps.secondsToDate(simulatorState.lastTxNumber + 1n);
|
|
81
|
+
const context = yield* Simulator.nextBlockContext(nextNumber);
|
|
82
|
+
return yield* Simulator.apply(simulatorState, tx, new WellFormedStrictness(), context, blockFullness);
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
fastForward(lastTxNumber) {
|
|
86
|
+
return SubscriptionRef.modify(this.#stateRef, (simulatorState) => {
|
|
87
|
+
return [
|
|
88
|
+
undefined,
|
|
89
|
+
{
|
|
90
|
+
...simulatorState,
|
|
91
|
+
lastTxNumber,
|
|
92
|
+
lastTx: undefined,
|
|
93
|
+
lastTxResult: undefined,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -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,57 @@
|
|
|
1
|
+
import { Deferred, Effect, Encoding, Exit, pipe, Scope } from 'effect';
|
|
2
|
+
import { NodeClient, PolkadotNodeClient, SubmissionEvent as SubmissionEventImported, } from '@midnight-ntwrk/wallet-sdk-node-client/effect';
|
|
3
|
+
import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
|
|
4
|
+
export const SubmissionEvent = SubmissionEventImported;
|
|
5
|
+
export const makeDefaultSubmissionService = (config) => {
|
|
6
|
+
const scopeAndClientDeferred = Deferred.make().pipe(Effect.runSync);
|
|
7
|
+
const makeScopeAndClient = Effect.gen(function* () {
|
|
8
|
+
const scope = yield* Scope.make();
|
|
9
|
+
const client = yield* PolkadotNodeClient.make({
|
|
10
|
+
nodeURL: config.relayURL,
|
|
11
|
+
}).pipe(Effect.provideService(Scope.Scope, scope));
|
|
12
|
+
return { scope, client };
|
|
13
|
+
});
|
|
14
|
+
void pipe(scopeAndClientDeferred, Deferred.complete(makeScopeAndClient), Effect.runPromise);
|
|
15
|
+
const submit = (transaction, waitForStatus = 'InBlock') => {
|
|
16
|
+
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)));
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
submitTransaction: submit,
|
|
20
|
+
close() {
|
|
21
|
+
return pipe(scopeAndClientDeferred, Deferred.await, Effect.flatMap(({ scope }) => Scope.close(scope, Exit.void)), Effect.ignoreLogged);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export const makeSimulatorSubmissionService = (waitForStatus = 'InBlock') => (config) => {
|
|
26
|
+
const submit = (transaction) => {
|
|
27
|
+
const serializedTx = transaction.serialize();
|
|
28
|
+
return config.simulator.submitRegularTx(transaction).pipe(Effect.map((output) => {
|
|
29
|
+
// Let's mimic node's client behavior here
|
|
30
|
+
switch (waitForStatus) {
|
|
31
|
+
case 'Submitted':
|
|
32
|
+
return SubmissionEvent.Submitted({
|
|
33
|
+
tx: serializedTx,
|
|
34
|
+
txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
|
|
35
|
+
});
|
|
36
|
+
case 'InBlock':
|
|
37
|
+
return SubmissionEvent.InBlock({
|
|
38
|
+
tx: serializedTx,
|
|
39
|
+
blockHash: output.blockHash,
|
|
40
|
+
blockHeight: output.blockNumber,
|
|
41
|
+
txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
|
|
42
|
+
});
|
|
43
|
+
case 'Finalized':
|
|
44
|
+
return SubmissionEvent.Finalized({
|
|
45
|
+
tx: serializedTx,
|
|
46
|
+
blockHash: output.blockHash,
|
|
47
|
+
blockHeight: output.blockNumber,
|
|
48
|
+
txHash: Encoding.encodeHex(serializedTx.subarray(0, 32)),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
submitTransaction: submit,
|
|
55
|
+
close: () => Effect.void,
|
|
56
|
+
};
|
|
57
|
+
};
|
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,123 @@
|
|
|
1
|
+
import { Effect, Either, Layer, ParseResult, pipe, Schema, Stream } from 'effect';
|
|
2
|
+
import { Event as LedgerEvent, LedgerParameters } from '@midnight-ntwrk/ledger-v6';
|
|
3
|
+
import { BlockHash, DustLedgerEvents } from '@midnight-ntwrk/wallet-sdk-indexer-client';
|
|
4
|
+
import { WsSubscriptionClient, HttpQueryClient, ConnectionHelper, } from '@midnight-ntwrk/wallet-sdk-indexer-client/effect';
|
|
5
|
+
import { DateOps, EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
|
|
6
|
+
import { WsURL } from '@midnight-ntwrk/wallet-sdk-utilities/networking';
|
|
7
|
+
import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
|
|
8
|
+
import { Uint8ArraySchema } from './Serialization.js';
|
|
9
|
+
export const SecretKeysResource = {
|
|
10
|
+
create: (secretKey) => {
|
|
11
|
+
let sk = secretKey;
|
|
12
|
+
return (cb) => {
|
|
13
|
+
if (sk === null || sk === undefined) {
|
|
14
|
+
throw new Error('Secret key has been consumed');
|
|
15
|
+
}
|
|
16
|
+
const result = cb(sk);
|
|
17
|
+
sk = null;
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const LedgerEventSchema = Schema.declare((input) => input instanceof LedgerEvent).annotations({
|
|
23
|
+
identifier: 'ledger.Event',
|
|
24
|
+
});
|
|
25
|
+
const LedgerEventFromUInt8Array = Schema.asSchema(Schema.transformOrFail(Uint8ArraySchema, LedgerEventSchema, {
|
|
26
|
+
encode: (e) => {
|
|
27
|
+
return Effect.try({
|
|
28
|
+
try: () => e.serialize(),
|
|
29
|
+
catch: (err) => {
|
|
30
|
+
return new ParseResult.Unexpected(err, 'Could not serialize Ledger Event');
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
decode: (bytes) => Effect.try({
|
|
35
|
+
try: () => LedgerEvent.deserialize(bytes),
|
|
36
|
+
catch: (err) => {
|
|
37
|
+
return new ParseResult.Unexpected(err, 'Could not deserialize Ledger Event');
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
const HexedEvent = pipe(Schema.Uint8ArrayFromHex, Schema.compose(LedgerEventFromUInt8Array));
|
|
42
|
+
export const SyncEventsUpdateSchema = Schema.Struct({
|
|
43
|
+
id: Schema.Number,
|
|
44
|
+
raw: HexedEvent,
|
|
45
|
+
maxId: Schema.Number,
|
|
46
|
+
});
|
|
47
|
+
export const WalletSyncUpdate = {
|
|
48
|
+
create: (update, secretKey) => {
|
|
49
|
+
return {
|
|
50
|
+
update,
|
|
51
|
+
secretKeys: SecretKeysResource.create(secretKey),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export const makeDefaultSyncService = (config) => {
|
|
56
|
+
const indexerSyncService = makeIndexerSyncService(config);
|
|
57
|
+
return {
|
|
58
|
+
updates: (state, secretKey) => {
|
|
59
|
+
return pipe(indexerSyncService.subscribeWallet(state), Stream.map((data) => WalletSyncUpdate.create(data, secretKey)), Stream.provideSomeLayer(indexerSyncService.connectionLayer()));
|
|
60
|
+
},
|
|
61
|
+
ledgerParameters: () => {
|
|
62
|
+
return Effect.gen(function* () {
|
|
63
|
+
const query = yield* BlockHash;
|
|
64
|
+
const result = yield* query({ offset: null });
|
|
65
|
+
return result.block?.ledgerParameters;
|
|
66
|
+
}).pipe(Effect.provide(indexerSyncService.queryClient()), Effect.scoped, Effect.catchAll((err) => Effect.fail(WalletError.WalletError.other(`Encountered unexpected error: ${err.message}`))), Effect.flatMap((ledgerParameters) => {
|
|
67
|
+
if (ledgerParameters === undefined) {
|
|
68
|
+
return Effect.fail(WalletError.WalletError.other('Unable to fetch ledger parameters'));
|
|
69
|
+
}
|
|
70
|
+
return LedgerOps.ledgerTry(() => LedgerParameters.deserialize(Buffer.from(ledgerParameters, 'hex')));
|
|
71
|
+
}));
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
export const makeIndexerSyncService = (config) => {
|
|
76
|
+
return {
|
|
77
|
+
queryClient() {
|
|
78
|
+
return pipe(HttpQueryClient.layer({ url: config.indexerClientConnection.indexerHttpUrl }), Layer.mapError((error) => WalletError.WalletError.other(error)));
|
|
79
|
+
},
|
|
80
|
+
connectionLayer() {
|
|
81
|
+
const { indexerClientConnection } = config;
|
|
82
|
+
return ConnectionHelper.createWebSocketUrl(indexerClientConnection.indexerHttpUrl, indexerClientConnection.indexerWsUrl).pipe(Either.flatMap((url) => WsURL.make(url)), Either.match({
|
|
83
|
+
onLeft: (error) => Layer.fail(error),
|
|
84
|
+
onRight: (url) => WsSubscriptionClient.layer({ url }),
|
|
85
|
+
}), Layer.mapError((e) => new WalletError.SyncWalletError({ message: 'Failed to to obtain correct indexer URLs', cause: e })));
|
|
86
|
+
},
|
|
87
|
+
subscribeWallet(state) {
|
|
88
|
+
const { appliedIndex } = state.progress;
|
|
89
|
+
return pipe(DustLedgerEvents.run({
|
|
90
|
+
id: Number(appliedIndex),
|
|
91
|
+
}), 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)));
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
export const makeDefaultSyncCapability = () => {
|
|
96
|
+
return {
|
|
97
|
+
applyUpdate(state, wrappedUpdate) {
|
|
98
|
+
const { update, secretKeys } = wrappedUpdate;
|
|
99
|
+
const nextIndex = BigInt(update.id);
|
|
100
|
+
const highestRelevantWalletIndex = BigInt(update.maxId);
|
|
101
|
+
// in case the nextIndex is less than or equal to the current appliedIndex
|
|
102
|
+
// just update highestRelevantWalletIndex
|
|
103
|
+
if (nextIndex <= state.progress.appliedIndex) {
|
|
104
|
+
return state.updateProgress({ highestRelevantWalletIndex, isConnected: true });
|
|
105
|
+
}
|
|
106
|
+
const events = [update.raw].filter((event) => event !== null);
|
|
107
|
+
return secretKeys((keys) => state
|
|
108
|
+
.applyEvents(keys, events, new Date())
|
|
109
|
+
.updateProgress({ appliedIndex: nextIndex, highestRelevantWalletIndex, isConnected: true }));
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export const makeSimulatorSyncService = (config) => {
|
|
114
|
+
return {
|
|
115
|
+
updates: (_state, secretKey) => config.simulator.state$.pipe(Stream.map((state) => ({ update: state, secretKey }))),
|
|
116
|
+
ledgerParameters: () => pipe(config.simulator.getLatestState(), Effect.map((state) => state.ledger.parameters)),
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
export const makeSimulatorSyncCapability = () => ({
|
|
120
|
+
applyUpdate: (state, update) => state
|
|
121
|
+
.applyEvents(update.secretKey, update.update.lastTxResult?.events || [], DateOps.secondsToDate(update.update.lastTxNumber))
|
|
122
|
+
.updateProgress({ appliedIndex: update.update.lastTxNumber }),
|
|
123
|
+
});
|
|
@@ -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
|
+
}
|