@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 ADDED
@@ -0,0 +1,3 @@
1
+ # Wallet SDK Dust Wallet
2
+
3
+ TBD
@@ -0,0 +1,19 @@
1
+ import { DustCoreWallet } from './DustCoreWallet.js';
2
+ import { DustGenerationInfo, DustToken, DustTokenFullInfo } from './types/Dust.js';
3
+ export type Balance = bigint;
4
+ export type CoinWithValue<TToken> = {
5
+ token: TToken;
6
+ value: Balance;
7
+ };
8
+ export type CoinSelection<TInput> = (coins: readonly CoinWithValue<TInput>[], amountNeeded: Balance) => CoinWithValue<TInput>[];
9
+ export declare const chooseCoin: <TInput>(coins: readonly CoinWithValue<TInput>[], amountNeeded: Balance) => CoinWithValue<TInput>[];
10
+ export type CoinsAndBalancesCapability<TState> = {
11
+ getWalletBalance(state: TState, time: Date): Balance;
12
+ getAvailableCoins(state: TState): readonly DustToken[];
13
+ getPendingCoins(state: TState): readonly DustToken[];
14
+ getTotalCoins(state: TState): ReadonlyArray<DustToken>;
15
+ getAvailableCoinsWithGeneratedDust(state: TState, currentTime: Date): ReadonlyArray<CoinWithValue<DustToken>>;
16
+ getAvailableCoinsWithFullInfo(state: TState, blockTime: Date): readonly DustTokenFullInfo[];
17
+ getGenerationInfo(state: TState, token: DustToken): DustGenerationInfo | undefined;
18
+ };
19
+ export declare const makeDefaultCoinsAndBalancesCapability: () => CoinsAndBalancesCapability<DustCoreWallet>;
@@ -0,0 +1,78 @@
1
+ import { Array, pipe } from 'effect';
2
+ import { updatedValue } from '@midnight-ntwrk/ledger-v6';
3
+ import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
4
+ export const chooseCoin = (coins, amountNeeded) => {
5
+ let sum = 0n;
6
+ const sorted = coins.toSorted((a, b) => Number(a.value - b.value));
7
+ const result = [];
8
+ for (const coin of sorted) {
9
+ sum += coin.value;
10
+ result.push(coin);
11
+ if (sum >= amountNeeded)
12
+ break;
13
+ }
14
+ return result;
15
+ };
16
+ export const makeDefaultCoinsAndBalancesCapability = () => {
17
+ const getWalletBalance = (state, time) => {
18
+ return state.state.walletBalance(time);
19
+ };
20
+ const getAvailableCoins = (state) => {
21
+ const pendingSpends = new Set([...state.pendingDustTokens.values()].map((coin) => coin.nonce));
22
+ return pipe(state.state.utxos, Array.filter((coin) => !pendingSpends.has(coin.nonce)));
23
+ };
24
+ const getPendingCoins = (state) => state.pendingDustTokens;
25
+ const getTotalCoins = (state) => [
26
+ ...getAvailableCoins(state),
27
+ ...getPendingCoins(state),
28
+ ];
29
+ const getGenerationInfo = (state, token) => {
30
+ const info = state.state.generationInfo(token);
31
+ return info && info.dtime
32
+ ? {
33
+ ...info,
34
+ dtime: new Date(+info.dtime), // TODO: remove when the ledger start to return a date instead of the number
35
+ }
36
+ : info;
37
+ };
38
+ const getAvailableCoinsWithGeneratedDust = (state, currentTime) => {
39
+ const result = [];
40
+ const available = getAvailableCoins(state);
41
+ for (const coin of available) {
42
+ const genInfo = getGenerationInfo(state, coin);
43
+ if (genInfo) {
44
+ const generatedValue = updatedValue(coin.ctime, coin.initialValue, genInfo, currentTime, state.state.params);
45
+ result.push({ token: coin, value: generatedValue });
46
+ }
47
+ }
48
+ return result;
49
+ };
50
+ const getAvailableCoinsWithFullInfo = (state, blockTime) => {
51
+ const result = [];
52
+ const available = getAvailableCoins(state);
53
+ for (const coin of available) {
54
+ const genInfo = getGenerationInfo(state, coin);
55
+ if (genInfo) {
56
+ const generatedValue = updatedValue(coin.ctime, coin.initialValue, genInfo, blockTime, state.state.params);
57
+ result.push({
58
+ token: coin,
59
+ dtime: genInfo.dtime,
60
+ maxCap: genInfo.value * state.state.params.nightDustRatio,
61
+ maxCapReachedAt: DateOps.addSeconds(coin.ctime, state.state.params.timeToCapSeconds),
62
+ generatedNow: generatedValue,
63
+ rate: genInfo.value * state.state.params.generationDecayRate,
64
+ });
65
+ }
66
+ }
67
+ return result;
68
+ };
69
+ return {
70
+ getWalletBalance,
71
+ getAvailableCoins,
72
+ getPendingCoins,
73
+ getTotalCoins,
74
+ getAvailableCoinsWithGeneratedDust,
75
+ getAvailableCoinsWithFullInfo,
76
+ getGenerationInfo,
77
+ };
78
+ };
@@ -0,0 +1,31 @@
1
+ import { Bindingish, DustLocalState, DustNullifier, DustParameters, DustPublicKey, DustSecretKey, Proofish, Signaturish, Transaction, Event } from '@midnight-ntwrk/ledger-v6';
2
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
+ import { SyncProgress } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { DustToken, DustTokenWithNullifier } from './types/Dust.js';
5
+ import { CoinWithValue } from './CoinsAndBalances.js';
6
+ import { NetworkId, UnprovenDustSpend } from './types/ledger.js';
7
+ export type PublicKey = {
8
+ publicKey: DustPublicKey;
9
+ };
10
+ export declare const PublicKey: {
11
+ fromSecretKey: (secretKey: DustSecretKey) => PublicKey;
12
+ };
13
+ export declare class DustCoreWallet {
14
+ readonly state: DustLocalState;
15
+ readonly publicKey: PublicKey;
16
+ readonly protocolVersion: ProtocolVersion.ProtocolVersion;
17
+ readonly progress: SyncProgress.SyncProgress;
18
+ readonly networkId: NetworkId;
19
+ readonly pendingDustTokens: Array<DustTokenWithNullifier>;
20
+ constructor(state: DustLocalState, publicKey: PublicKey, networkId: NetworkId, pendingDustTokens?: Array<DustTokenWithNullifier>, syncProgress?: Omit<SyncProgress.SyncProgressData, 'isConnected'>, protocolVersion?: ProtocolVersion.ProtocolVersion);
21
+ static init(localState: DustLocalState, secretKey: DustSecretKey, networkId: NetworkId): DustCoreWallet;
22
+ static readonly initEmpty: (dustParameters: DustParameters, secretKey: DustSecretKey, networkId: NetworkId) => DustCoreWallet;
23
+ static empty(localState: DustLocalState, publicKey: PublicKey, networkId: NetworkId): DustCoreWallet;
24
+ static restore(localState: DustLocalState, publicKey: PublicKey, pendingTokens: Array<DustTokenWithNullifier>, syncProgress: SyncProgress.SyncProgressData, protocolVersion: bigint, networkId: NetworkId): DustCoreWallet;
25
+ applyEvents(secretKey: DustSecretKey, events: Event[], currentTime: Date): DustCoreWallet;
26
+ applyFailed(tx: Transaction<Signaturish, Proofish, Bindingish>): DustCoreWallet;
27
+ revertTransaction<TTransaction extends Transaction<Signaturish, Proofish, Bindingish>>(tx: TTransaction): DustCoreWallet;
28
+ updateProgress({ appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }: Partial<SyncProgress.SyncProgressData>): DustCoreWallet;
29
+ spendCoins(secretKey: DustSecretKey, coins: ReadonlyArray<CoinWithValue<DustToken>>, currentTime: Date): [ReadonlyArray<UnprovenDustSpend>, DustCoreWallet];
30
+ static pendingDustTokensToMap(tokens: Array<DustTokenWithNullifier>): Map<DustNullifier, DustToken>;
31
+ }
@@ -0,0 +1,96 @@
1
+ import { DustLocalState, } from '@midnight-ntwrk/ledger-v6';
2
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
+ import { SyncProgress } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { DateOps } from '@midnight-ntwrk/wallet-sdk-utilities';
5
+ import { Array as Arr, pipe } from 'effect';
6
+ export const PublicKey = {
7
+ fromSecretKey: (secretKey) => {
8
+ return {
9
+ publicKey: secretKey.publicKey,
10
+ };
11
+ },
12
+ };
13
+ export class DustCoreWallet {
14
+ state;
15
+ publicKey;
16
+ protocolVersion;
17
+ progress;
18
+ networkId;
19
+ pendingDustTokens;
20
+ constructor(state, publicKey, networkId, pendingDustTokens = [], syncProgress, protocolVersion = ProtocolVersion.MinSupportedVersion) {
21
+ this.state = state;
22
+ this.publicKey = publicKey;
23
+ this.networkId = networkId;
24
+ this.pendingDustTokens = pendingDustTokens;
25
+ this.protocolVersion = protocolVersion;
26
+ this.progress = syncProgress ? SyncProgress.createSyncProgress(syncProgress) : SyncProgress.createSyncProgress();
27
+ }
28
+ static init(localState, secretKey, networkId) {
29
+ return new DustCoreWallet(localState, PublicKey.fromSecretKey(secretKey), networkId);
30
+ }
31
+ static initEmpty = (dustParameters, secretKey, networkId) => {
32
+ return DustCoreWallet.empty(new DustLocalState(dustParameters), PublicKey.fromSecretKey(secretKey), networkId);
33
+ };
34
+ static empty(localState, publicKey, networkId) {
35
+ return new DustCoreWallet(localState, publicKey, networkId);
36
+ }
37
+ static restore(localState, publicKey, pendingTokens, syncProgress, protocolVersion, networkId) {
38
+ return new DustCoreWallet(localState, publicKey, networkId, pendingTokens, syncProgress, ProtocolVersion.ProtocolVersion(protocolVersion));
39
+ }
40
+ applyEvents(secretKey, events, currentTime) {
41
+ if (!events.length)
42
+ return this;
43
+ const updatedState = this.state.replayEvents(secretKey, events).processTtls(currentTime);
44
+ let updatedPending = this.pendingDustTokens;
45
+ if (updatedPending.length) {
46
+ const newAvailable = updatedState.utxos.map((utxo) => utxo.nonce);
47
+ updatedPending = updatedPending.filter((pendingToken) => newAvailable.includes(pendingToken.nonce));
48
+ }
49
+ return new DustCoreWallet(updatedState, this.publicKey, this.networkId, updatedPending, this.progress);
50
+ }
51
+ applyFailed(tx) {
52
+ const removedPending = [];
53
+ let updatedState = this.state;
54
+ if (tx.intents) {
55
+ const pendingTokensMap = DustCoreWallet.pendingDustTokensToMap(this.pendingDustTokens);
56
+ for (const intent of tx.intents.values()) {
57
+ if (intent.dustActions && intent.dustActions.spends) {
58
+ for (const spend of intent.dustActions.spends) {
59
+ const pending = pendingTokensMap.get(spend.oldNullifier);
60
+ if (pending === undefined)
61
+ continue;
62
+ removedPending.push(spend.oldNullifier);
63
+ updatedState = updatedState.processTtls(DateOps.addSeconds(pending.ctime, this.state.params.dustGracePeriodSeconds));
64
+ }
65
+ }
66
+ }
67
+ }
68
+ const pendingLeft = this.pendingDustTokens.filter((token) => !removedPending.includes(token.nullifier));
69
+ return new DustCoreWallet(updatedState, this.publicKey, this.networkId, pendingLeft, this.progress);
70
+ }
71
+ revertTransaction(tx) {
72
+ return this.applyFailed(tx);
73
+ }
74
+ updateProgress({ appliedIndex, highestRelevantWalletIndex, highestIndex, highestRelevantIndex, isConnected, }) {
75
+ const updatedProgress = SyncProgress.createSyncProgress({
76
+ appliedIndex: appliedIndex ?? this.progress.appliedIndex,
77
+ highestRelevantWalletIndex: highestRelevantWalletIndex ?? this.progress.highestRelevantWalletIndex,
78
+ highestIndex: highestIndex ?? this.progress.highestIndex,
79
+ highestRelevantIndex: highestRelevantIndex ?? this.progress.highestRelevantIndex,
80
+ isConnected: isConnected ?? this.progress.isConnected,
81
+ });
82
+ return new DustCoreWallet(this.state, this.publicKey, this.networkId, this.pendingDustTokens, updatedProgress);
83
+ }
84
+ spendCoins(secretKey, coins, currentTime) {
85
+ const [output, newState, newPending] = pipe(coins, Arr.reduce([[], this.state, this.pendingDustTokens], ([spends, localState], { token: coinToSpend, value: takeFee }) => {
86
+ const [newState, dustSpend] = localState.spend(secretKey, coinToSpend, takeFee, currentTime);
87
+ const newPending = [...this.pendingDustTokens, { ...coinToSpend, nullifier: dustSpend.oldNullifier }];
88
+ return [Arr.append(spends, dustSpend), newState, newPending];
89
+ }));
90
+ const updatedState = new DustCoreWallet(newState, this.publicKey, this.networkId, newPending, this.progress);
91
+ return [output, updatedState];
92
+ }
93
+ static pendingDustTokensToMap(tokens) {
94
+ return new Map(tokens.map(({ nullifier, ...token }) => [nullifier, token]));
95
+ }
96
+ }
@@ -0,0 +1,51 @@
1
+ import { DustParameters, DustPublicKey, DustSecretKey, FinalizedTransaction, Signature, SignatureVerifyingKey, UnprovenTransaction } from '@midnight-ntwrk/ledger-v6';
2
+ import { ProtocolState, ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
+ import { Variant, WalletLike } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
4
+ import { ProvingRecipe, TransactionHistory } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
+ import * as rx from 'rxjs';
6
+ import { Balance, CoinsAndBalancesCapability } from './CoinsAndBalances.js';
7
+ import { DustCoreWallet } from './DustCoreWallet.js';
8
+ import { KeysCapability } from './Keys.js';
9
+ import { SerializationCapability } from './Serialization.js';
10
+ import { SubmitTransactionMethod } from './Submission.js';
11
+ import { DustToken, DustTokenFullInfo, UtxoWithMeta } from './types/Dust.js';
12
+ import { AnyTransaction, NetworkId } from './types/ledger.js';
13
+ import { DefaultV1Configuration, DefaultV1Variant } from './V1Builder.js';
14
+ export type DustWalletCapabilities = {
15
+ serialization: SerializationCapability<DustCoreWallet, null, string>;
16
+ coinsAndBalances: CoinsAndBalancesCapability<DustCoreWallet>;
17
+ keys: KeysCapability<DustCoreWallet>;
18
+ };
19
+ export declare class DustWalletState {
20
+ static readonly mapState: (capabilities: DustWalletCapabilities) => (state: ProtocolState.ProtocolState<DustCoreWallet>) => DustWalletState;
21
+ readonly protocolVersion: ProtocolVersion.ProtocolVersion;
22
+ readonly state: DustCoreWallet;
23
+ readonly capabilities: DustWalletCapabilities;
24
+ get totalCoins(): readonly DustToken[];
25
+ get availableCoins(): readonly DustToken[];
26
+ get pendingCoins(): readonly DustToken[];
27
+ get dustPublicKey(): DustPublicKey;
28
+ get dustAddress(): string;
29
+ get progress(): TransactionHistory.ProgressUpdate;
30
+ constructor(state: ProtocolState.ProtocolState<DustCoreWallet>, capabilities: DustWalletCapabilities);
31
+ walletBalance(time: Date): Balance;
32
+ availableCoinsWithFullInfo(time: Date): readonly DustTokenFullInfo[];
33
+ serialize(): string;
34
+ }
35
+ export interface DustWallet extends WalletLike.WalletLike<[Variant.VersionedVariant<DefaultV1Variant>]> {
36
+ readonly state: rx.Observable<DustWalletState>;
37
+ start(secretKey: DustSecretKey): Promise<void>;
38
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: Array<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Promise<UnprovenTransaction>;
39
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Promise<ProvingRecipe.ProvingRecipe<FinalizedTransaction>>;
40
+ calculateFee(transaction: AnyTransaction): Promise<bigint>;
41
+ addFeePayment(secretKey: DustSecretKey, transaction: UnprovenTransaction, currentTime: Date, ttl: Date): Promise<ProvingRecipe.ProvingRecipe<FinalizedTransaction>>;
42
+ finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<FinalizedTransaction>): Promise<FinalizedTransaction>;
43
+ readonly submitTransaction: SubmitTransactionMethod<FinalizedTransaction>;
44
+ serializeState(): Promise<string>;
45
+ waitForSyncedState(allowedGap?: bigint): Promise<DustWalletState>;
46
+ }
47
+ export interface DustWalletClass extends WalletLike.BaseWalletClass<[Variant.VersionedVariant<DefaultV1Variant>]> {
48
+ startWithSeed(seed: Uint8Array, dustParameters: DustParameters, networkId: NetworkId): DustWallet;
49
+ restore(serializedState: string): DustWallet;
50
+ }
51
+ export declare function DustWallet(configuration: DefaultV1Configuration): DustWalletClass;
@@ -0,0 +1,128 @@
1
+ import { DustSecretKey, } from '@midnight-ntwrk/ledger-v6';
2
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
3
+ import { WalletBuilder } from '@midnight-ntwrk/wallet-sdk-runtime';
4
+ import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
5
+ import { Effect, Either } from 'effect';
6
+ import * as rx from 'rxjs';
7
+ import { DustCoreWallet } from './DustCoreWallet.js';
8
+ import { V1Tag } from './RunningV1Variant.js';
9
+ import { V1Builder } from './V1Builder.js';
10
+ export class DustWalletState {
11
+ static mapState = (capabilities) => (state) => {
12
+ return new DustWalletState(state, capabilities);
13
+ };
14
+ protocolVersion;
15
+ state;
16
+ capabilities;
17
+ get totalCoins() {
18
+ return this.capabilities.coinsAndBalances.getTotalCoins(this.state);
19
+ }
20
+ get availableCoins() {
21
+ return this.capabilities.coinsAndBalances.getAvailableCoins(this.state);
22
+ }
23
+ get pendingCoins() {
24
+ return this.capabilities.coinsAndBalances.getPendingCoins(this.state);
25
+ }
26
+ get dustPublicKey() {
27
+ return this.capabilities.keys.getDustPublicKey(this.state);
28
+ }
29
+ get dustAddress() {
30
+ return DustAddress.encodePublicKey(this.state.networkId, this.dustPublicKey);
31
+ }
32
+ get progress() {
33
+ return {
34
+ appliedIndex: this.state.progress.appliedIndex,
35
+ highestRelevantWalletIndex: this.state.progress.highestRelevantWalletIndex,
36
+ highestIndex: this.state.progress.highestIndex,
37
+ highestRelevantIndex: this.state.progress.highestRelevantIndex,
38
+ };
39
+ }
40
+ constructor(state, capabilities) {
41
+ this.protocolVersion = state.version;
42
+ this.state = state.state;
43
+ this.capabilities = capabilities;
44
+ }
45
+ walletBalance(time) {
46
+ return this.capabilities.coinsAndBalances.getWalletBalance(this.state, time);
47
+ }
48
+ availableCoinsWithFullInfo(time) {
49
+ return this.capabilities.coinsAndBalances.getAvailableCoinsWithFullInfo(this.state, time);
50
+ }
51
+ serialize() {
52
+ return this.capabilities.serialization.serialize(this.state);
53
+ }
54
+ }
55
+ export function DustWallet(configuration) {
56
+ const BaseWallet = WalletBuilder.init()
57
+ .withVariant(ProtocolVersion.MinSupportedVersion, new V1Builder().withDefaults())
58
+ .build(configuration);
59
+ return class DustWalletImplementation extends BaseWallet {
60
+ static startWithSeed(seed, dustParameters, networkId) {
61
+ const dustSecretKey = DustSecretKey.fromSeed(seed);
62
+ return DustWalletImplementation.startFirst(DustWalletImplementation, DustCoreWallet.initEmpty(dustParameters, dustSecretKey, networkId));
63
+ }
64
+ static restore(serializedState) {
65
+ const deserialized = DustWalletImplementation.allVariantsRecord()[V1Tag].variant.deserializeState(serializedState)
66
+ .pipe(Either.getOrThrow);
67
+ return DustWalletImplementation.startFirst(DustWalletImplementation, deserialized);
68
+ }
69
+ state;
70
+ constructor(runtime, scope) {
71
+ super(runtime, scope);
72
+ this.state = this.rawState.pipe(rx.map(DustWalletState.mapState(DustWalletImplementation.allVariantsRecord()[V1Tag].variant)), rx.shareReplay({ refCount: true, bufferSize: 1 }));
73
+ }
74
+ start(secretKey) {
75
+ return this.runtime.dispatch({ [V1Tag]: (v1) => v1.startSyncInBackground(secretKey) }).pipe(Effect.runPromise);
76
+ }
77
+ createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress) {
78
+ return this.runtime
79
+ .dispatch({
80
+ [V1Tag]: (v1) => v1.createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress),
81
+ })
82
+ .pipe(Effect.runPromise);
83
+ }
84
+ addDustGenerationSignature(transaction, signature) {
85
+ return this.runtime
86
+ .dispatch({
87
+ [V1Tag]: (v1) => v1.addDustGenerationSignature(transaction, signature),
88
+ })
89
+ .pipe(Effect.runPromise);
90
+ }
91
+ calculateFee(transaction) {
92
+ return this.runtime
93
+ .dispatch({
94
+ [V1Tag]: (v1) => v1.calculateFee(transaction),
95
+ })
96
+ .pipe(Effect.runPromise);
97
+ }
98
+ addFeePayment(secretKey, transaction, currentTime, ttl) {
99
+ return this.runtime
100
+ .dispatch({
101
+ [V1Tag]: (v1) => v1.addFeePayment(secretKey, transaction, currentTime, ttl),
102
+ })
103
+ .pipe(Effect.runPromise);
104
+ }
105
+ finalizeTransaction(recipe) {
106
+ return this.runtime
107
+ .dispatch({
108
+ [V1Tag]: (v1) => v1.finalizeTransaction(recipe),
109
+ })
110
+ .pipe(Effect.runPromise);
111
+ }
112
+ submitTransaction = ((tx, waitForStatus = 'InBlock') => {
113
+ return this.runtime
114
+ .dispatch({ [V1Tag]: (v1) => v1.submitTransaction(tx, waitForStatus) })
115
+ .pipe(Effect.runPromise);
116
+ });
117
+ waitForSyncedState(allowedGap = 0n) {
118
+ return rx.firstValueFrom(this.state.pipe(rx.filter((state) => state.state.progress.isCompleteWithin(allowedGap))));
119
+ }
120
+ /**
121
+ * Serializes the most recent state
122
+ * It's preferable to use [[DustWalletState.serialize]] instead, to know exactly, which state is serialized
123
+ */
124
+ serializeState() {
125
+ return rx.firstValueFrom(this.state).then((state) => state.serialize());
126
+ }
127
+ };
128
+ }
package/dist/Keys.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { DustPublicKey } from '@midnight-ntwrk/ledger-v6';
2
+ import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
3
+ import { DustCoreWallet } from './DustCoreWallet.js';
4
+ export type KeysCapability<TState> = {
5
+ getDustPublicKey(state: TState): DustPublicKey;
6
+ getDustAddress(state: TState): DustAddress;
7
+ };
8
+ export declare const makeDefaultKeysCapability: () => KeysCapability<DustCoreWallet>;
package/dist/Keys.js ADDED
@@ -0,0 +1,11 @@
1
+ import { DustAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
2
+ export const makeDefaultKeysCapability = () => {
3
+ return {
4
+ getDustPublicKey: (state) => {
5
+ return state.publicKey.publicKey;
6
+ },
7
+ getDustAddress: (state) => {
8
+ return new DustAddress(state.publicKey.publicKey);
9
+ },
10
+ };
11
+ };
@@ -0,0 +1,44 @@
1
+ import { Effect, Stream, Scope } from 'effect';
2
+ import { DustSecretKey, Signature, SignatureVerifyingKey, FinalizedTransaction, UnprovenTransaction } from '@midnight-ntwrk/ledger-v6';
3
+ import { Proving, ProvingRecipe, WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
4
+ import { WalletRuntimeError, Variant, StateChange } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
5
+ import { DustToken, UtxoWithMeta } from './types/Dust.js';
6
+ import { KeysCapability } from './Keys.js';
7
+ import { SyncCapability, SyncService } from './Sync.js';
8
+ import { SimulatorState } from './Simulator.js';
9
+ import { CoinsAndBalancesCapability, CoinSelection } from './CoinsAndBalances.js';
10
+ import { TransactingCapability } from './Transacting.js';
11
+ import { SubmissionService, SubmitTransactionMethod } from './Submission.js';
12
+ import { DustCoreWallet } from './DustCoreWallet.js';
13
+ import { SerializationCapability } from './Serialization.js';
14
+ import { AnyTransaction } from './types/ledger.js';
15
+ export declare namespace RunningV1Variant {
16
+ type Context<TSerialized, TSyncUpdate, TTransaction, TStartAux> = {
17
+ serializationCapability: SerializationCapability<DustCoreWallet, null, TSerialized>;
18
+ syncService: SyncService<DustCoreWallet, TStartAux, TSyncUpdate>;
19
+ syncCapability: SyncCapability<DustCoreWallet, TSyncUpdate>;
20
+ transactingCapability: TransactingCapability<DustSecretKey, DustCoreWallet, TTransaction>;
21
+ provingService: Proving.ProvingService<TTransaction>;
22
+ coinsAndBalancesCapability: CoinsAndBalancesCapability<DustCoreWallet>;
23
+ keysCapability: KeysCapability<DustCoreWallet>;
24
+ submissionService: SubmissionService<TTransaction>;
25
+ coinSelection: CoinSelection<DustToken>;
26
+ };
27
+ type AnyContext = Context<any, any, any, any>;
28
+ }
29
+ export declare const V1Tag: unique symbol;
30
+ export type DefaultRunningV1 = RunningV1Variant<string, SimulatorState, FinalizedTransaction, DustSecretKey>;
31
+ export declare class RunningV1Variant<TSerialized, TSyncUpdate, TTransaction, TStartAux> implements Variant.RunningVariant<typeof V1Tag, DustCoreWallet> {
32
+ #private;
33
+ __polyTag__: typeof V1Tag;
34
+ readonly state: Stream.Stream<StateChange.StateChange<DustCoreWallet>, WalletRuntimeError>;
35
+ constructor(scope: Scope.Scope, context: Variant.VariantContext<DustCoreWallet>, v1Context: RunningV1Variant.Context<TSerialized, TSyncUpdate, TTransaction, TStartAux>);
36
+ startSyncInBackground(startAux: TStartAux): Effect.Effect<void>;
37
+ startSync(startAux: TStartAux): Stream.Stream<void, WalletError.WalletError, Scope.Scope>;
38
+ createDustGenerationTransaction(currentTime: Date, ttl: Date, nightUtxos: ReadonlyArray<UtxoWithMeta>, nightVerifyingKey: SignatureVerifyingKey, dustReceiverAddress: string | undefined): Effect.Effect<UnprovenTransaction, WalletError.WalletError>;
39
+ addDustGenerationSignature(transaction: UnprovenTransaction, signature: Signature): Effect.Effect<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
40
+ calculateFee(transaction: AnyTransaction): Effect.Effect<bigint, WalletError.WalletError>;
41
+ addFeePayment(secretKey: DustSecretKey, transaction: UnprovenTransaction, currentTime: Date, ttl: Date): Effect.Effect<ProvingRecipe.ProvingRecipe<FinalizedTransaction>, WalletError.WalletError>;
42
+ finalizeTransaction(recipe: ProvingRecipe.ProvingRecipe<TTransaction>): Effect.Effect<TTransaction, WalletError.WalletError>;
43
+ submitTransaction: SubmitTransactionMethod<TTransaction>;
44
+ }
@@ -0,0 +1,113 @@
1
+ import { Effect, SubscriptionRef, Stream, pipe, Scope, Sink, Console, Duration, Schedule } from 'effect';
2
+ import { nativeToken, updatedValue, } from '@midnight-ntwrk/ledger-v6';
3
+ import { ProtocolVersion } from '@midnight-ntwrk/wallet-sdk-abstractions';
4
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
5
+ import { EitherOps, LedgerOps } from '@midnight-ntwrk/wallet-sdk-utilities';
6
+ import { StateChange, VersionChangeType, } from '@midnight-ntwrk/wallet-sdk-runtime/abstractions';
7
+ const progress = (state) => {
8
+ const appliedIndex = state.progress?.appliedIndex ?? 0n;
9
+ const highestRelevantWalletIndex = state.progress?.highestRelevantWalletIndex ?? 0n;
10
+ const highestIndex = state.progress?.highestIndex ?? 0n;
11
+ const highestRelevantIndex = state.progress?.highestRelevantIndex ?? 0n;
12
+ const sourceGap = highestIndex - highestRelevantIndex;
13
+ const applyGap = highestRelevantWalletIndex - appliedIndex;
14
+ return [StateChange.ProgressUpdate({ sourceGap, applyGap })];
15
+ };
16
+ const protocolVersionChange = (previous, current) => {
17
+ return previous.protocolVersion != current.protocolVersion
18
+ ? [
19
+ StateChange.VersionChange({
20
+ change: VersionChangeType.Version({
21
+ version: ProtocolVersion.ProtocolVersion(current.protocolVersion),
22
+ }),
23
+ }),
24
+ ]
25
+ : [];
26
+ };
27
+ export const V1Tag = Symbol('V1');
28
+ export class RunningV1Variant {
29
+ __polyTag__ = V1Tag;
30
+ #scope;
31
+ #context;
32
+ #v1Context;
33
+ state;
34
+ constructor(scope, context, v1Context) {
35
+ this.#scope = scope;
36
+ this.#context = context;
37
+ this.#v1Context = v1Context;
38
+ this.state = Stream.fromEffect(context.stateRef.get).pipe(Stream.flatMap((initialState) => context.stateRef.changes.pipe(Stream.mapAccum(initialState, (previous, current) => {
39
+ return [current, [previous, current]];
40
+ }))), Stream.mapConcat(([previous, current]) => {
41
+ // TODO: emit progress only upon actual change
42
+ return [
43
+ StateChange.State({ state: current }),
44
+ ...progress(current),
45
+ ...protocolVersionChange(previous, current),
46
+ ];
47
+ }));
48
+ }
49
+ startSyncInBackground(startAux) {
50
+ return this.startSync(startAux).pipe(Stream.runScoped(Sink.drain), Effect.forkScoped, Effect.provideService(Scope.Scope, this.#scope));
51
+ }
52
+ startSync(startAux) {
53
+ return pipe(SubscriptionRef.get(this.#context.stateRef), Stream.fromEffect, Stream.flatMap((state) => this.#v1Context.syncService.updates(state, startAux)), Stream.mapEffect((update) => {
54
+ return SubscriptionRef.updateEffect(this.#context.stateRef, (state) => Effect.try({
55
+ try: () => this.#v1Context.syncCapability.applyUpdate(state, update),
56
+ catch: (err) => new WalletError.OtherWalletError({
57
+ message: 'Error while applying sync update',
58
+ cause: err,
59
+ }),
60
+ }));
61
+ }), Stream.tapError((error) => Console.error(error)), Stream.retry(pipe(Schedule.exponential(Duration.seconds(1), 2), Schedule.map((delay) => {
62
+ const maxDelay = Duration.minutes(2);
63
+ const jitter = Duration.millis(Math.floor(Math.random() * 1000));
64
+ const delayWithJitter = Duration.toMillis(delay) + Duration.toMillis(jitter);
65
+ return Duration.millis(Math.min(delayWithJitter, Duration.toMillis(maxDelay)));
66
+ }))));
67
+ }
68
+ createDustGenerationTransaction(currentTime, ttl, nightUtxos, nightVerifyingKey, dustReceiverAddress) {
69
+ if (nightUtxos.some((utxo) => utxo.type !== nativeToken().raw)) {
70
+ return Effect.fail(WalletError.WalletError.other('Token of a non-Night type received'));
71
+ }
72
+ return Effect.Do.pipe(Effect.bind('currentState', () => SubscriptionRef.get(this.#context.stateRef)), Effect.bind('utxosWithDustValue', ({ currentState }) => LedgerOps.ledgerTry(() => {
73
+ const dustPublicKey = this.#v1Context.keysCapability.getDustPublicKey(currentState);
74
+ return nightUtxos.map((utxo) => {
75
+ const genInfo = {
76
+ value: utxo.value,
77
+ owner: dustPublicKey,
78
+ nonce: LedgerOps.randomNonce(),
79
+ dtime: undefined,
80
+ };
81
+ const dustValue = updatedValue(utxo.ctime, 0n, genInfo, currentTime, currentState.state.params);
82
+ return { token: utxo, value: dustValue };
83
+ });
84
+ })), Effect.flatMap(({ utxosWithDustValue }) => {
85
+ return this.#v1Context.transactingCapability
86
+ .createDustGenerationTransaction(currentTime, ttl, utxosWithDustValue, nightVerifyingKey, dustReceiverAddress)
87
+ .pipe(EitherOps.toEffect);
88
+ }));
89
+ }
90
+ addDustGenerationSignature(transaction, signature) {
91
+ return this.#v1Context.transactingCapability
92
+ .addDustGenerationSignature(transaction, signature)
93
+ .pipe(EitherOps.toEffect);
94
+ }
95
+ calculateFee(transaction) {
96
+ return pipe(this.#v1Context.syncService.ledgerParameters(), Effect.map((params) => this.#v1Context.transactingCapability.calculateFee(transaction, params)));
97
+ }
98
+ addFeePayment(secretKey, transaction, currentTime, ttl) {
99
+ return SubscriptionRef.modifyEffect(this.#context.stateRef, (state) => {
100
+ return pipe(this.#v1Context.syncService.ledgerParameters(), Effect.flatMap((params) => this.#v1Context.transactingCapability.addFeePayment(secretKey, state, transaction, currentTime, ttl, params)), Effect.map(({ recipe, newState }) => [recipe, newState]));
101
+ });
102
+ }
103
+ finalizeTransaction(recipe) {
104
+ return this.#v1Context.provingService
105
+ .prove(recipe)
106
+ .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revertRecipe(state, recipe)))));
107
+ }
108
+ submitTransaction = ((transaction, waitForStatus = 'InBlock') => {
109
+ return this.#v1Context.submissionService
110
+ .submitTransaction(transaction, waitForStatus)
111
+ .pipe(Effect.tapError(() => SubscriptionRef.updateEffect(this.#context.stateRef, (state) => EitherOps.toEffect(this.#v1Context.transactingCapability.revert(state, transaction)))));
112
+ });
113
+ }
@@ -0,0 +1,9 @@
1
+ import { Either, Schema } from 'effect';
2
+ import { WalletError } from '@midnight-ntwrk/wallet-sdk-shielded/v1';
3
+ import { DustCoreWallet } from './DustCoreWallet.js';
4
+ export type SerializationCapability<TWallet, TAux, TSerialized> = {
5
+ serialize(wallet: TWallet): TSerialized;
6
+ deserialize(aux: TAux, data: TSerialized): Either.Either<TWallet, WalletError.WalletError>;
7
+ };
8
+ export declare const Uint8ArraySchema: Schema.declare<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, readonly [], never>;
9
+ export declare const makeDefaultV1SerializationCapability: () => SerializationCapability<DustCoreWallet, null, string>;