@midnight-ntwrk/wallet-sdk-shielded 3.0.0 → 3.0.2
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 +7 -0
- package/dist/v1/RunningV1Variant.js +8 -1
- package/dist/v1/Sync.d.ts +4 -0
- package/dist/v1/Sync.js +39 -15
- package/dist/v1/Transacting.js +6 -7
- package/package.json +12 -10
package/README.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
> [!IMPORTANT]
|
|
2
|
+
> **This package has moved.** The `@midnight-ntwrk` scope is published only
|
|
3
|
+
> during the migration window and will stop receiving updates. Please migrate to
|
|
4
|
+
> [`@midnightntwrk/wallet-sdk-shielded`](https://www.npmjs.com/package/@midnightntwrk/wallet-sdk-shielded).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
1
8
|
# @midnight-ntwrk/wallet-sdk-shielded
|
|
2
9
|
|
|
3
10
|
Manages shielded tokens on the Midnight network using zero-knowledge proofs.
|
|
@@ -58,7 +58,14 @@ export class RunningV1Variant {
|
|
|
58
58
|
message: 'Error while applying sync update',
|
|
59
59
|
cause: err,
|
|
60
60
|
}),
|
|
61
|
-
})).pipe(Effect.flatMap(({ changes, protocolVersion }) =>
|
|
61
|
+
})).pipe(Effect.flatMap(({ changes, protocolVersion }) =>
|
|
62
|
+
// Skip the tx-history fork entirely when there are no changes.
|
|
63
|
+
// Forking unconditionally allocates a fiber per apply call (one
|
|
64
|
+
// for every batch the sync emits, even the all-progress ones),
|
|
65
|
+
// which adds up fast during catch-up.
|
|
66
|
+
changes.length === 0
|
|
67
|
+
? Effect.void
|
|
68
|
+
: pipe(Effect.forEach(changes, (change) => pipe(this.#v1Context.transactionHistoryService.getTransactionDetails(change.source), Effect.flatMap((metadata) => this.#v1Context.transactionHistoryService.put(change, metadata, protocolVersion)), Effect.catchAllCause((cause) => Console.error('Error processing tx history metadata', cause))), { discard: true, concurrency: 'unbounded' }), Effect.forkScoped)), Effect.provideService(Scope.Scope, this.#scope))), Stream.tapError((error) => Console.error(error)), Stream.retry(pipe(Schedule.exponential(Duration.seconds(1), 2), Schedule.map((delay) => {
|
|
62
69
|
const maxDelay = Duration.minutes(2);
|
|
63
70
|
const jitter = Duration.millis(Math.floor(Math.random() * 1000));
|
|
64
71
|
const delayWithJitter = Duration.toMillis(delay) + Duration.toMillis(jitter);
|
package/dist/v1/Sync.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export type IndexerClientConnection = {
|
|
|
18
18
|
indexerHttpUrl: string;
|
|
19
19
|
indexerWsUrl?: string;
|
|
20
20
|
keepAlive?: number;
|
|
21
|
+
/** Cap on the in-flight event queue between the WebSocket push and the apply loop. Default: 10000. */
|
|
22
|
+
bufferSize?: number;
|
|
23
|
+
/** In-flight count at which the disposed WS subscription is reopened. Default: 100. */
|
|
24
|
+
resumeThreshold?: number;
|
|
21
25
|
};
|
|
22
26
|
export type BatchUpdatesConfig = {
|
|
23
27
|
/**
|
package/dist/v1/Sync.js
CHANGED
|
@@ -94,10 +94,36 @@ export const makeEventsSyncService = (config) => {
|
|
|
94
94
|
}
|
|
95
95
|
const indexerWsUrl = indexerWsUrlResult.right;
|
|
96
96
|
const appliedIndex = state.progress?.appliedIndex ?? 0n;
|
|
97
|
+
// The boundary is load-bearing, not waste: this subscription emits only events (no tip/progress
|
|
98
|
+
// sentinel), and `isConnected`/the tip (`maxId`) are set only when an event is received. So the
|
|
99
|
+
// cursor must stay `<= appliedIndex` — never `appliedIndex + 1`. Requesting one event later would
|
|
100
|
+
// deliver nothing to a wallet already at the tip, so `applyUpdate` would never run and sync would
|
|
101
|
+
// hang.
|
|
102
|
+
//
|
|
103
|
+
// A fresh wallet has `appliedIndex === 0n` (the "nothing applied yet" sentinel), so `resumeFrom`
|
|
104
|
+
// is `-1n` and the `variables` mapping below opens the subscription with no `id` — the indexer
|
|
105
|
+
// streams from the very start. A restored wallet has `appliedIndex >= 1`, so `resumeFrom` is
|
|
106
|
+
// `appliedIndex - 1` and the inclusive cursor re-delivers the boundary event.
|
|
107
|
+
const resumeFrom = appliedIndex - 1n;
|
|
97
108
|
const batchSize = config.batchUpdates?.size ?? 10;
|
|
98
109
|
const batchTimeout = Duration.millis(config.batchUpdates?.timeout ?? 1);
|
|
99
110
|
const batchSpacing = config.batchUpdates?.spacing ?? 4;
|
|
100
|
-
const
|
|
111
|
+
const bufferSize = config.indexerClientConnection.bufferSize ?? 10000;
|
|
112
|
+
const resumeThreshold = config.indexerClientConnection.resumeThreshold ?? 100;
|
|
113
|
+
const eventsStream = pipe(
|
|
114
|
+
// Backpressure caps the in-flight queue between the WS push and the
|
|
115
|
+
// apply loop. Without it the JS heap grows linearly with catch-up
|
|
116
|
+
// depth, since `Stream.asyncPush({ bufferSize: 'unbounded' })`
|
|
117
|
+
// buffers every event the indexer pushes regardless of apply rate.
|
|
118
|
+
ZswapEvents.runWithBackpressure({
|
|
119
|
+
bufferSize,
|
|
120
|
+
resumeThreshold,
|
|
121
|
+
from: resumeFrom,
|
|
122
|
+
// `resumeFrom < 0n` means a fresh wallet: send no `id` so the indexer streams from the very
|
|
123
|
+
// start, rather than relying on `id: 0` sorting below the first real event id.
|
|
124
|
+
variables: (cursor) => ({ id: cursor < 0n ? null : Number(cursor) }),
|
|
125
|
+
key: (r) => BigInt(r.zswapLedgerEvents.id),
|
|
126
|
+
}), Stream.provideLayer(WsSubscriptionClient.layer({ url: indexerWsUrl, keepAlive: config.indexerClientConnection.keepAlive })), Stream.mapError((error) => new SyncWalletError(error)), Stream.mapEffect((subscription) => pipe(subscription.zswapLedgerEvents, Schema.decodeUnknownEither(EventsSyncUpdateFromPayload), Either.mapLeft((err) => new SyncWalletError(err)), EitherOps.toEffect)), Stream.groupedWithin(batchSize, batchTimeout), Stream.map(Chunk.toArray), Stream.map((data) => WalletSyncUpdate.create(data, secretKeys)));
|
|
101
127
|
return batchSpacing > 0
|
|
102
128
|
? Stream.schedule(eventsStream, Schedule.spaced(Duration.millis(batchSpacing)))
|
|
103
129
|
: eventsStream;
|
|
@@ -110,24 +136,22 @@ export const makeEventsSyncCapability = () => {
|
|
|
110
136
|
if (wrappedUpdate.updates.length === 0) {
|
|
111
137
|
return [state, { changes: [], protocolVersion: Number(state.protocolVersion) }];
|
|
112
138
|
}
|
|
139
|
+
// The subscription resumes at the last-applied index and its cursor is inclusive, so the
|
|
140
|
+
// boundary event (id === appliedIndex) is re-delivered. Replaying it would re-insert
|
|
141
|
+
// commitments already in the zswap state ("non-linear insertion" error), so only events
|
|
142
|
+
// strictly after the applied index are replayed. The boundary is still used (below) to
|
|
143
|
+
// refresh the tip and mark the wallet connected — that is how an already-caught-up wallet
|
|
144
|
+
// learns it is synced when no new events exist.
|
|
145
|
+
const appliedIndex = state.progress?.appliedIndex ?? 0n;
|
|
146
|
+
const freshUpdates = wrappedUpdate.updates.filter((u) => BigInt(u.id) > appliedIndex);
|
|
113
147
|
const lastUpdate = wrappedUpdate.updates.at(-1);
|
|
114
|
-
const nextIndex = BigInt(lastUpdate.id);
|
|
115
148
|
const highestRelevantWalletIndex = BigInt(lastUpdate.maxId);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return [
|
|
120
|
-
CoreWallet.updateProgress(state, {
|
|
121
|
-
highestRelevantWalletIndex,
|
|
122
|
-
isConnected: true,
|
|
123
|
-
}),
|
|
124
|
-
{ changes: [], protocolVersion: lastUpdate.protocolVersion },
|
|
125
|
-
];
|
|
126
|
-
}
|
|
127
|
-
const [newState, newChanges] = CoreWallet.replayEventsWithChanges(state, wrappedUpdate.secretKeys, wrappedUpdate.updates.map((u) => u.event));
|
|
149
|
+
const [newState, newChanges] = freshUpdates.length === 0
|
|
150
|
+
? [state, []]
|
|
151
|
+
: CoreWallet.replayEventsWithChanges(state, wrappedUpdate.secretKeys, freshUpdates.map((u) => u.event));
|
|
128
152
|
const updatedState = CoreWallet.updateProgress(newState, {
|
|
129
153
|
highestRelevantWalletIndex,
|
|
130
|
-
appliedIndex:
|
|
154
|
+
appliedIndex: freshUpdates.length === 0 ? appliedIndex : BigInt(freshUpdates.at(-1).id),
|
|
131
155
|
isConnected: true,
|
|
132
156
|
});
|
|
133
157
|
return [updatedState, { changes: newChanges, protocolVersion: lastUpdate.protocolVersion }];
|
package/dist/v1/Transacting.js
CHANGED
|
@@ -46,9 +46,7 @@ export class TransactingCapabilityImplementation {
|
|
|
46
46
|
}
|
|
47
47
|
const { newState: afterFallible, fallibleOffers } = yield* this.balanceFallibleSection(secretKeys, state, initialImbalances, coinSelection);
|
|
48
48
|
const { newState: afterGuaranteed, offer: guaranteed } = yield* this.#balanceGuaranteedSection(secretKeys, afterFallible, initialImbalances, coinSelection, Imbalances.empty());
|
|
49
|
-
const balancedTx = ledger.Transaction.fromParts(this.networkId, guaranteed);
|
|
50
|
-
// workaround as Transaction.fromParts does not accept fallible offers with specific segments
|
|
51
|
-
balancedTx.fallibleOffer = fallibleOffers.size > 0 ? fallibleOffers : undefined;
|
|
49
|
+
const balancedTx = Array.from(fallibleOffers.entries()).reduce((tx, [segment, offer]) => tx.addZswapOffer({ tag: 'specific', value: segment }, offer), ledger.Transaction.fromParts(this.networkId, guaranteed));
|
|
52
50
|
return [balancedTx, afterGuaranteed];
|
|
53
51
|
});
|
|
54
52
|
}
|
|
@@ -193,10 +191,11 @@ export class TransactingCapabilityImplementation {
|
|
|
193
191
|
}
|
|
194
192
|
},
|
|
195
193
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
})
|
|
194
|
+
// An empty recipe (e.g. the guaranteed section is already balanced) yields no offer.
|
|
195
|
+
// Treat that as "nothing to add" rather than a failure, mirroring the fallible section.
|
|
196
|
+
return pipe(this.#prepareOffer(secretKeys, state, balanceRecipe, 0), Option.match({
|
|
197
|
+
onNone: () => ({ offer: undefined, newState: state }),
|
|
198
|
+
onSome: (result) => result,
|
|
200
199
|
}));
|
|
201
200
|
});
|
|
202
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midnight-ntwrk/wallet-sdk-shielded",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
"author": "Midnight Foundation",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"publishConfig": {
|
|
11
|
-
"registry": "https://
|
|
11
|
+
"registry": "https://registry.npmjs.org/",
|
|
12
|
+
"access": "public"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"dist/"
|
|
15
16
|
],
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/midnight-
|
|
19
|
+
"url": "git+https://github.com/midnightntwrk/midnight-wallet.git",
|
|
20
|
+
"directory": "packages/shielded-wallet"
|
|
19
21
|
},
|
|
20
22
|
"exports": {
|
|
21
23
|
".": {
|
|
@@ -28,13 +30,13 @@
|
|
|
28
30
|
}
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
31
|
-
"@midnight-ntwrk/ledger-v8": "^8.0
|
|
32
|
-
"@midnight-ntwrk/wallet-sdk-abstractions": "2.1.0",
|
|
33
|
-
"@midnight-ntwrk/wallet-sdk-address-format": "3.1.
|
|
34
|
-
"@midnight-ntwrk/wallet-sdk-capabilities": "3.3.
|
|
35
|
-
"@midnight-ntwrk/wallet-sdk-indexer-client": "1.2.
|
|
36
|
-
"@midnight-ntwrk/wallet-sdk-runtime": "1.0.
|
|
37
|
-
"@midnight-ntwrk/wallet-sdk-utilities": "1.
|
|
33
|
+
"@midnight-ntwrk/ledger-v8": "^8.1.0",
|
|
34
|
+
"@midnight-ntwrk/wallet-sdk-abstractions": "^2.1.0",
|
|
35
|
+
"@midnight-ntwrk/wallet-sdk-address-format": "^3.1.2",
|
|
36
|
+
"@midnight-ntwrk/wallet-sdk-capabilities": "^3.3.1",
|
|
37
|
+
"@midnight-ntwrk/wallet-sdk-indexer-client": "^1.2.3",
|
|
38
|
+
"@midnight-ntwrk/wallet-sdk-runtime": "^1.0.5",
|
|
39
|
+
"@midnight-ntwrk/wallet-sdk-utilities": "^1.2.0",
|
|
38
40
|
"effect": "^3.19.19",
|
|
39
41
|
"rxjs": "^7.8.2"
|
|
40
42
|
},
|