@effectstream/midnight-contracts 0.104.1 → 0.200.1
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 -3
- package/package.json +40 -22
- package/src/build-wallet.ts +3 -3
- package/src/deploy-phased.ts +3 -3
- package/src/deploy.ts +7 -7
- package/src/get-wallet-info-cli.ts +21 -34
- package/src/get-wallet-info.ts +232 -85
- package/src/midnight-env.ts +17 -19
- package/src/mod.ts +4 -0
- package/src/providers.ts +27 -12
- package/src/types.ts +6 -5
- package/test/integration-rc-stack.ts +223 -0
- package/test/migration.test.ts +219 -0
- package/test/migration.typecheck.ts +27 -0
- package/tsconfig.json +8 -0
package/src/get-wallet-info.ts
CHANGED
|
@@ -15,15 +15,19 @@ import {
|
|
|
15
15
|
DustSecretKey,
|
|
16
16
|
LedgerParameters,
|
|
17
17
|
ZswapSecretKeys,
|
|
18
|
-
|
|
19
|
-
} from "@
|
|
18
|
+
nativeToken,
|
|
19
|
+
} from "@midnightntwrk/ledger-v9";
|
|
20
20
|
|
|
21
21
|
const log = console;
|
|
22
22
|
import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id";
|
|
23
23
|
import { Buffer } from "node:buffer";
|
|
24
24
|
import * as Rx from "rxjs";
|
|
25
25
|
import { HDWallet, Roles } from "@midnightntwrk/wallet-sdk-hd";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
type DefaultConfiguration,
|
|
28
|
+
type UnprovenTransactionRecipe,
|
|
29
|
+
WalletFacade,
|
|
30
|
+
} from "@midnightntwrk/wallet-sdk-facade";
|
|
27
31
|
import { DustWallet } from "@midnightntwrk/wallet-sdk-dust-wallet";
|
|
28
32
|
import {
|
|
29
33
|
UnshieldedWallet,
|
|
@@ -37,7 +41,7 @@ import {
|
|
|
37
41
|
NetworkId,
|
|
38
42
|
TransactionHistoryStorage,
|
|
39
43
|
} from "@midnightntwrk/wallet-sdk-abstractions";
|
|
40
|
-
import {
|
|
44
|
+
import { DustAddress } from "@midnightntwrk/wallet-sdk-address-format";
|
|
41
45
|
// No node:fs / node:path here — this module is reachable from BROWSER bundles
|
|
42
46
|
// (MidnightLocal.connectFromSeed imports `./wallet-info` to build the wallet
|
|
43
47
|
// facade), and browser bundlers shim those to empty/null modules. All disk I/O
|
|
@@ -79,7 +83,9 @@ function deriveSeedForRole(seed: string, role: DerivationRole): Uint8Array {
|
|
|
79
83
|
throw new Error(`Key derivation out of bounds for role: ${role}`);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
const derivedKey = Buffer.from(derivationResult.key);
|
|
87
|
+
hdWalletResult.hdWallet.clear();
|
|
88
|
+
return derivedKey;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
// ============================================================================
|
|
@@ -145,6 +151,67 @@ export function resolveFacadeDustBalance(
|
|
|
145
151
|
return w > b ? w : b;
|
|
146
152
|
}
|
|
147
153
|
|
|
154
|
+
/** Count spendable DUST UTXOs from a facade state or dust-wallet state. */
|
|
155
|
+
export function resolveFacadeDustAvailableCoins(
|
|
156
|
+
facadeOrDustState: unknown,
|
|
157
|
+
): number {
|
|
158
|
+
const dust =
|
|
159
|
+
(facadeOrDustState as { dust?: unknown } | null | undefined)?.dust ??
|
|
160
|
+
facadeOrDustState;
|
|
161
|
+
if (dust === undefined || dust === null || typeof dust !== "object") return 0;
|
|
162
|
+
const availableCoins = (dust as { availableCoins?: unknown }).availableCoins;
|
|
163
|
+
return Array.isArray(availableCoins) ? availableCoins.length : 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface DustFundsReadiness {
|
|
167
|
+
/** Aggregate wallet balance, retained for diagnostics only. */
|
|
168
|
+
balance: bigint;
|
|
169
|
+
/** Coins the Ledger-v9 DUST wallet currently exposes as available. */
|
|
170
|
+
availableCoins: number;
|
|
171
|
+
/** Available coins that meet the caller's optional per-coin value floor. */
|
|
172
|
+
spendableCoins: number;
|
|
173
|
+
/** The spendability signal adapters must use instead of aggregate balance. */
|
|
174
|
+
ready: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolve Ledger-v9 DUST spendability without inferring it from aggregate
|
|
179
|
+
* balance. Regular wallet flows count every available coin; fee-balancing
|
|
180
|
+
* callers can provide a value floor so newly-created, value-insufficient coins
|
|
181
|
+
* do not advertise readiness prematurely.
|
|
182
|
+
*/
|
|
183
|
+
export function resolveFacadeDustFundsReadiness(
|
|
184
|
+
facadeOrDustState: unknown,
|
|
185
|
+
minSpendableDustPerCoin?: bigint,
|
|
186
|
+
): DustFundsReadiness {
|
|
187
|
+
const dust =
|
|
188
|
+
(facadeOrDustState as { dust?: unknown } | null | undefined)?.dust ??
|
|
189
|
+
facadeOrDustState;
|
|
190
|
+
const coins = dust && typeof dust === "object"
|
|
191
|
+
? (dust as { availableCoins?: unknown }).availableCoins
|
|
192
|
+
: undefined;
|
|
193
|
+
const available = Array.isArray(coins) ? coins : [];
|
|
194
|
+
const spendableCoins = minSpendableDustPerCoin === undefined
|
|
195
|
+
? available.length
|
|
196
|
+
: available.filter((coin) => {
|
|
197
|
+
try {
|
|
198
|
+
const generatedNow = (coin as { generatedNow?: bigint | string | number } | null)
|
|
199
|
+
?.generatedNow;
|
|
200
|
+
return generatedNow !== undefined &&
|
|
201
|
+
BigInt(generatedNow) >= minSpendableDustPerCoin;
|
|
202
|
+
} catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}).length;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
balance: resolveFacadeDustBalance(facadeOrDustState),
|
|
209
|
+
availableCoins: available.length,
|
|
210
|
+
spendableCoins,
|
|
211
|
+
ready: spendableCoins > 0,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
148
215
|
function resolveDustHeartbeatPollMs(waitNonZero: boolean, override?: number): number {
|
|
149
216
|
if (override !== undefined) return Math.max(0, Math.floor(override));
|
|
150
217
|
if (!waitNonZero) return 0;
|
|
@@ -276,8 +343,8 @@ export async function syncAndWaitForFunds(
|
|
|
276
343
|
let latestState: any = null;
|
|
277
344
|
const periodicLogger = setInterval(() => {
|
|
278
345
|
if (!latestState) return;
|
|
279
|
-
const shieldedSynced = latestState.shielded.
|
|
280
|
-
const dustSynced = latestState.dust.
|
|
346
|
+
const shieldedSynced = latestState.shielded.progress.isStrictlyComplete();
|
|
347
|
+
const dustSynced = latestState.dust.progress.isStrictlyComplete();
|
|
281
348
|
const unshieldedSynced = latestState.unshielded?.progress?.isStrictlyComplete() ?? false;
|
|
282
349
|
log.info(
|
|
283
350
|
`[wait] shielded=${shieldedSynced}, unshielded=${unshieldedSynced}, dust=${dustSynced}`
|
|
@@ -285,45 +352,51 @@ export async function syncAndWaitForFunds(
|
|
|
285
352
|
}, CONSTANTS.WALLET_SYNC_THROTTLE_MS);
|
|
286
353
|
const syncStartedAt = Date.now();
|
|
287
354
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
355
|
+
let state: any;
|
|
356
|
+
try {
|
|
357
|
+
state = await Rx.firstValueFrom(
|
|
358
|
+
wallet.state().pipe(
|
|
359
|
+
Rx.throttleTime(CONSTANTS.WALLET_SYNC_THROTTLE_MS),
|
|
360
|
+
Rx.tap((nextState: any) => {
|
|
361
|
+
latestState = nextState;
|
|
362
|
+
const elapsedSec = ((Date.now() - syncStartedAt) / 1000).toFixed(0);
|
|
363
|
+
const shieldedSynced = nextState.shielded.progress.isStrictlyComplete();
|
|
364
|
+
const dustSynced = nextState.dust.progress.isStrictlyComplete();
|
|
365
|
+
const unshieldedSynced = nextState.unshielded?.progress?.isStrictlyComplete() ?? false;
|
|
366
|
+
log.info(
|
|
367
|
+
`Wallet sync progress (${elapsedSec}s): shielded=${shieldedSynced}, unshielded=${unshieldedSynced}, dust=${dustSynced}`
|
|
368
|
+
);
|
|
369
|
+
}),
|
|
370
|
+
Rx.filter((nextState: any) => {
|
|
371
|
+
const shieldedSynced = skipShielded || nextState.shielded.progress.isStrictlyComplete();
|
|
372
|
+
const dustSynced = nextState.dust.progress.isStrictlyComplete();
|
|
373
|
+
const unshieldedSynced = nextState.unshielded?.progress?.isStrictlyComplete() ?? false;
|
|
304
374
|
|
|
305
|
-
|
|
375
|
+
if (!shieldedSynced || !dustSynced || !unshieldedSynced) return false;
|
|
306
376
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
377
|
+
if (waitNonZero && !skipShielded) {
|
|
378
|
+
const tokenId = nativeToken().raw;
|
|
379
|
+
const shieldedBalance = nextState.shielded.balances[tokenId] ?? 0n;
|
|
380
|
+
return shieldedBalance > 0n;
|
|
381
|
+
}
|
|
312
382
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
383
|
+
return true;
|
|
384
|
+
}),
|
|
385
|
+
Rx.tap(() => log.info("Wallet sync complete")),
|
|
386
|
+
Rx.timeout({
|
|
387
|
+
each: syncTimeoutMs,
|
|
388
|
+
with: () =>
|
|
389
|
+
Rx.throwError(
|
|
390
|
+
() => new Error(`Wallet sync timeout after ${syncTimeoutMs}ms`)
|
|
391
|
+
),
|
|
392
|
+
})
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
} finally {
|
|
396
|
+
clearInterval(periodicLogger);
|
|
397
|
+
}
|
|
325
398
|
|
|
326
|
-
const tokenId =
|
|
399
|
+
const tokenId = nativeToken().raw;
|
|
327
400
|
|
|
328
401
|
const shieldedBalancesObj = state.shielded.balances || {};
|
|
329
402
|
const availableKeys = Object.keys(shieldedBalancesObj);
|
|
@@ -356,7 +429,9 @@ export async function syncAndWaitForFunds(
|
|
|
356
429
|
* stopped) does not wedge on façade `combineLatest`.
|
|
357
430
|
*
|
|
358
431
|
* When `waitNonZero` is true, first waits for dust appliedIndex to reach tip,
|
|
359
|
-
* then polls the dust sub-wallet
|
|
432
|
+
* then polls the dust sub-wallet until at least one spendable DUST UTXO exists.
|
|
433
|
+
* A positive accrued balance is not sufficient because it can precede coin
|
|
434
|
+
* availability after NIGHT registration.
|
|
360
435
|
*/
|
|
361
436
|
export async function waitForDustFunds(
|
|
362
437
|
wallet: WalletFacade,
|
|
@@ -368,8 +443,10 @@ export async function waitForDustFunds(
|
|
|
368
443
|
dustPollIntervalMs?: number;
|
|
369
444
|
/** When true with waitNonZero, skip catch-up wait (e.g. fresh 0/0 chain). */
|
|
370
445
|
skipCatchUp?: boolean;
|
|
446
|
+
/** Optional generated-value floor for a coin to count as spendable. */
|
|
447
|
+
minSpendableDustPerCoin?: bigint;
|
|
371
448
|
},
|
|
372
|
-
): Promise<
|
|
449
|
+
): Promise<DustFundsReadiness> {
|
|
373
450
|
log.info("Waiting for dust wallet to sync and receive funds...");
|
|
374
451
|
|
|
375
452
|
const options = typeof optionsOrTimeout === 'number'
|
|
@@ -378,6 +455,7 @@ export async function waitForDustFunds(
|
|
|
378
455
|
|
|
379
456
|
const syncTimeoutMs = options?.timeoutMs ?? resolveWalletSyncTimeoutMs();
|
|
380
457
|
const waitNonZero = options?.waitNonZero ?? false;
|
|
458
|
+
const minSpendableDustPerCoin = options?.minSpendableDustPerCoin;
|
|
381
459
|
const pollMs = resolveDustHeartbeatPollMs(waitNonZero, options?.dustPollIntervalMs);
|
|
382
460
|
const deadline = Date.now() + syncTimeoutMs;
|
|
383
461
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
@@ -408,7 +486,7 @@ export async function waitForDustFunds(
|
|
|
408
486
|
? state$
|
|
409
487
|
: state$.pipe(Rx.throttleTime(CONSTANTS.WALLET_SYNC_THROTTLE_MS));
|
|
410
488
|
|
|
411
|
-
const
|
|
489
|
+
const dustFunds = await Rx.firstValueFrom(
|
|
412
490
|
sampled$.pipe(
|
|
413
491
|
Rx.tap((ds: unknown) => {
|
|
414
492
|
try {
|
|
@@ -416,17 +494,20 @@ export async function waitForDustFunds(
|
|
|
416
494
|
const at = new Date();
|
|
417
495
|
const p = dustProgressFromState(ds);
|
|
418
496
|
const bal = resolveFacadeDustBalance(ds, at);
|
|
497
|
+
const availableCoins = resolveFacadeDustAvailableCoins(ds);
|
|
419
498
|
log.info(
|
|
420
499
|
`Dust wallet sync progress (${elapsedSec}s): ` +
|
|
421
500
|
`${p?.appliedIndex ?? "?"}/${p?.highestRelevantWalletIndex ?? "?"} ` +
|
|
422
|
-
`balance=${bal}`,
|
|
501
|
+
`balance=${bal} availableCoins=${availableCoins}`,
|
|
423
502
|
);
|
|
424
503
|
} catch {
|
|
425
504
|
/* ignore */
|
|
426
505
|
}
|
|
427
506
|
}),
|
|
428
|
-
Rx.map((ds: unknown) =>
|
|
429
|
-
|
|
507
|
+
Rx.map((ds: unknown) =>
|
|
508
|
+
resolveFacadeDustFundsReadiness(ds, minSpendableDustPerCoin)
|
|
509
|
+
),
|
|
510
|
+
Rx.filter((funds) => !waitNonZero || funds.ready),
|
|
430
511
|
Rx.timeout({
|
|
431
512
|
first: balanceBudgetMs,
|
|
432
513
|
with: () =>
|
|
@@ -434,13 +515,16 @@ export async function waitForDustFunds(
|
|
|
434
515
|
() => new Error(`Dust wallet sync timeout after ${syncTimeoutMs}ms`)
|
|
435
516
|
),
|
|
436
517
|
}),
|
|
437
|
-
Rx.tap((
|
|
438
|
-
|
|
518
|
+
Rx.tap((funds) => {
|
|
519
|
+
log.info(
|
|
520
|
+
`Dust wallet ready=${funds.ready} balance=${funds.balance} ` +
|
|
521
|
+
`availableCoins=${funds.availableCoins} spendableCoins=${funds.spendableCoins}`,
|
|
522
|
+
);
|
|
439
523
|
})
|
|
440
524
|
)
|
|
441
|
-
)
|
|
525
|
+
);
|
|
442
526
|
|
|
443
|
-
return
|
|
527
|
+
return dustFunds;
|
|
444
528
|
}
|
|
445
529
|
|
|
446
530
|
// ============================================================================
|
|
@@ -469,6 +553,16 @@ export interface DustSyncWithRetryOptions {
|
|
|
469
553
|
syncMode?: WalletSyncMode;
|
|
470
554
|
/** Base directory for dust state files (relative to CWD). Default: "dust-state". */
|
|
471
555
|
dustStateDir?: string;
|
|
556
|
+
/** Optional generated-value floor for a coin to count as spendable. */
|
|
557
|
+
minSpendableDustPerCoin?: bigint;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export interface DustSyncWithRetryResult {
|
|
561
|
+
walletResult: WalletResult;
|
|
562
|
+
dustBalance: bigint;
|
|
563
|
+
availableDustCoins: number;
|
|
564
|
+
spendableDustCoins: number;
|
|
565
|
+
dustReady: boolean;
|
|
472
566
|
}
|
|
473
567
|
|
|
474
568
|
/**
|
|
@@ -486,7 +580,7 @@ export interface DustSyncWithRetryOptions {
|
|
|
486
580
|
export async function waitForDustFundsWithRetry(
|
|
487
581
|
options: DustSyncWithRetryOptions,
|
|
488
582
|
existingWalletResult?: WalletResult,
|
|
489
|
-
): Promise<
|
|
583
|
+
): Promise<DustSyncWithRetryResult> {
|
|
490
584
|
const {
|
|
491
585
|
networkUrls,
|
|
492
586
|
seed,
|
|
@@ -497,6 +591,7 @@ export async function waitForDustFundsWithRetry(
|
|
|
497
591
|
throttleMs = CONSTANTS.WALLET_SYNC_THROTTLE_MS,
|
|
498
592
|
syncMode = 'dust-only',
|
|
499
593
|
dustStateDir = DEFAULT_DUST_STATE_DIR,
|
|
594
|
+
minSpendableDustPerCoin,
|
|
500
595
|
} = options;
|
|
501
596
|
|
|
502
597
|
const networkIdStr = String(networkId);
|
|
@@ -572,7 +667,13 @@ export async function waitForDustFundsWithRetry(
|
|
|
572
667
|
const dustWallet = (walletResult.wallet as any).dust;
|
|
573
668
|
if (!dustWallet || !dustWallet.state) {
|
|
574
669
|
log.warn("Dust wallet state not available; skipping dust sync.");
|
|
575
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
walletResult,
|
|
672
|
+
dustBalance: 0n,
|
|
673
|
+
availableDustCoins: 0,
|
|
674
|
+
spendableDustCoins: 0,
|
|
675
|
+
dustReady: false,
|
|
676
|
+
};
|
|
576
677
|
}
|
|
577
678
|
|
|
578
679
|
// First, try the SDK's built-in waitForSyncedState with DUST_COMPLETION_GAP.
|
|
@@ -657,27 +758,42 @@ export async function waitForDustFundsWithRetry(
|
|
|
657
758
|
);
|
|
658
759
|
|
|
659
760
|
// Sync complete — read balance from dust sub-wallet (not façade).
|
|
660
|
-
let
|
|
761
|
+
let dustFunds: DustFundsReadiness = {
|
|
762
|
+
balance: 0n,
|
|
763
|
+
availableCoins: 0,
|
|
764
|
+
spendableCoins: 0,
|
|
765
|
+
ready: false,
|
|
766
|
+
};
|
|
661
767
|
try {
|
|
662
|
-
|
|
768
|
+
dustFunds = await waitForDustFunds(walletResult!.wallet, {
|
|
663
769
|
timeoutMs: balanceWaitTimeoutMs,
|
|
664
770
|
waitNonZero: true,
|
|
771
|
+
minSpendableDustPerCoin,
|
|
665
772
|
});
|
|
666
773
|
} catch {
|
|
667
|
-
// ignore —
|
|
774
|
+
// ignore — explicit readiness remains false
|
|
668
775
|
}
|
|
669
776
|
|
|
670
|
-
log.info(
|
|
777
|
+
log.info(
|
|
778
|
+
`Dust sync complete (attempt ${attempt}). ` +
|
|
779
|
+
`ready=${dustFunds.ready} balance=${dustFunds.balance} ` +
|
|
780
|
+
`availableCoins=${dustFunds.availableCoins} spendableCoins=${dustFunds.spendableCoins}`,
|
|
781
|
+
);
|
|
671
782
|
|
|
672
|
-
if (
|
|
783
|
+
if (!dustFunds.ready) {
|
|
673
784
|
log.info("No dust after sync; attempting unshielded NIGHT registration...");
|
|
674
785
|
try {
|
|
675
786
|
if (await registerNightForDust(walletResult!)) {
|
|
676
|
-
|
|
787
|
+
dustFunds = await waitForDustFunds(walletResult!.wallet, {
|
|
677
788
|
timeoutMs: balanceWaitTimeoutMs,
|
|
678
789
|
waitNonZero: true,
|
|
790
|
+
minSpendableDustPerCoin,
|
|
679
791
|
});
|
|
680
|
-
log.info(
|
|
792
|
+
log.info(
|
|
793
|
+
`Dust after registration: ready=${dustFunds.ready} ` +
|
|
794
|
+
`balance=${dustFunds.balance} availableCoins=${dustFunds.availableCoins} ` +
|
|
795
|
+
`spendableCoins=${dustFunds.spendableCoins}`,
|
|
796
|
+
);
|
|
681
797
|
}
|
|
682
798
|
} catch (e) {
|
|
683
799
|
log.warn(
|
|
@@ -686,8 +802,8 @@ export async function waitForDustFundsWithRetry(
|
|
|
686
802
|
}
|
|
687
803
|
}
|
|
688
804
|
|
|
689
|
-
if (
|
|
690
|
-
log.warn("
|
|
805
|
+
if (!dustFunds.ready) {
|
|
806
|
+
log.warn("No spendable DUST coin — wallet needs DUST or unshielded NIGHT to register.");
|
|
691
807
|
}
|
|
692
808
|
|
|
693
809
|
if (syncMode === 'dust-only') {
|
|
@@ -697,7 +813,13 @@ export async function waitForDustFundsWithRetry(
|
|
|
697
813
|
// Save final state to disk
|
|
698
814
|
await serializeAndSave(walletResult);
|
|
699
815
|
|
|
700
|
-
return {
|
|
816
|
+
return {
|
|
817
|
+
walletResult,
|
|
818
|
+
dustBalance: dustFunds.balance,
|
|
819
|
+
availableDustCoins: dustFunds.availableCoins,
|
|
820
|
+
spendableDustCoins: dustFunds.spendableCoins,
|
|
821
|
+
dustReady: dustFunds.ready,
|
|
822
|
+
};
|
|
701
823
|
} catch (e) {
|
|
702
824
|
const isStall = e instanceof Error && e.message === "stall";
|
|
703
825
|
|
|
@@ -769,7 +891,7 @@ function createWalletConfiguration(
|
|
|
769
891
|
relayURL: new URL(networkUrls.node.replace("http", "ws")),
|
|
770
892
|
networkId: networkId,
|
|
771
893
|
txHistoryStorage: new InMemoryTransactionHistoryStorage(
|
|
772
|
-
TransactionHistoryStorage.
|
|
894
|
+
TransactionHistoryStorage.TransactionHistoryEntryCommonSchema,
|
|
773
895
|
),
|
|
774
896
|
costParameters: {
|
|
775
897
|
additionalFeeOverhead: CONSTANTS.DUST_FEE_OVERHEAD,
|
|
@@ -845,7 +967,7 @@ function buildUnshieldedWallet(
|
|
|
845
967
|
return UnshieldedWallet({
|
|
846
968
|
...config,
|
|
847
969
|
txHistoryStorage: new InMemoryTransactionHistoryStorage(
|
|
848
|
-
TransactionHistoryStorage.
|
|
970
|
+
TransactionHistoryStorage.TransactionHistoryEntryCommonSchema,
|
|
849
971
|
),
|
|
850
972
|
} as any).startWithPublicKey(PublicKey.fromKeyStore(keystore));
|
|
851
973
|
}
|
|
@@ -866,26 +988,29 @@ export async function buildWalletFacade(
|
|
|
866
988
|
dustSerializedState?: string | null,
|
|
867
989
|
options?: { stopAuxWalletsImmediately?: boolean },
|
|
868
990
|
): Promise<WalletResult> {
|
|
991
|
+
setNetworkId(networkId);
|
|
869
992
|
const shieldedSeed = deriveSeedForRole(seed, Roles.Zswap);
|
|
870
993
|
const dustSeed = deriveSeedForRole(seed, Roles.Dust);
|
|
871
994
|
const unshieldedSeed = deriveSeedForRole(seed, Roles.NightExternal);
|
|
872
995
|
|
|
873
996
|
const config = createWalletConfiguration(networkUrls, networkId);
|
|
874
997
|
|
|
875
|
-
const unshieldedKeystore = createKeystore(
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const unshieldedWallet = buildUnshieldedWallet(config, unshieldedKeystore);
|
|
998
|
+
const unshieldedKeystore = createKeystore(
|
|
999
|
+
{ kind: "schnorr", secret: unshieldedSeed },
|
|
1000
|
+
networkId,
|
|
1001
|
+
);
|
|
880
1002
|
|
|
881
1003
|
const zswapSecretKeys = ZswapSecretKeys.fromSeed(shieldedSeed);
|
|
882
1004
|
const dustSecretKey = DustSecretKey.fromSeed(dustSeed);
|
|
883
1005
|
|
|
884
1006
|
const wallet: WalletFacade = await WalletFacade.init({
|
|
885
|
-
configuration: config
|
|
886
|
-
shielded: () =>
|
|
887
|
-
|
|
888
|
-
|
|
1007
|
+
configuration: config,
|
|
1008
|
+
shielded: (walletConfig) =>
|
|
1009
|
+
buildShieldedWallet(walletConfig, shieldedSeed),
|
|
1010
|
+
unshielded: (walletConfig) =>
|
|
1011
|
+
buildUnshieldedWallet(walletConfig, unshieldedKeystore),
|
|
1012
|
+
dust: (walletConfig) =>
|
|
1013
|
+
buildDustWallet(walletConfig, dustSeed, dustSerializedState),
|
|
889
1014
|
});
|
|
890
1015
|
|
|
891
1016
|
await wallet.start(zswapSecretKeys, dustSecretKey);
|
|
@@ -897,11 +1022,10 @@ export async function buildWalletFacade(
|
|
|
897
1022
|
}
|
|
898
1023
|
|
|
899
1024
|
const unshieldedAddress = unshieldedKeystore.getBech32Address().asString();
|
|
900
|
-
const
|
|
901
|
-
const dustAddress = MidnightBech32m.encode(
|
|
1025
|
+
const dustAddress = DustAddress.encodePublicKey(
|
|
902
1026
|
networkId,
|
|
903
|
-
|
|
904
|
-
)
|
|
1027
|
+
dustSecretKey.publicKey,
|
|
1028
|
+
);
|
|
905
1029
|
|
|
906
1030
|
return {
|
|
907
1031
|
wallet,
|
|
@@ -962,7 +1086,7 @@ export async function registerNightForDust(
|
|
|
962
1086
|
const state = await Rx.firstValueFrom(
|
|
963
1087
|
walletResult.wallet.state().pipe(
|
|
964
1088
|
Rx.filter((s: any) => {
|
|
965
|
-
const dustSynced = s.dust?.
|
|
1089
|
+
const dustSynced = s.dust?.progress?.isStrictlyComplete() ?? false;
|
|
966
1090
|
const unshieldedSynced = s.unshielded?.progress?.isStrictlyComplete() ?? false;
|
|
967
1091
|
return dustSynced && unshieldedSynced;
|
|
968
1092
|
}),
|
|
@@ -978,22 +1102,45 @@ export async function registerNightForDust(
|
|
|
978
1102
|
|
|
979
1103
|
if (unregisteredNightUtxos.length === 0) {
|
|
980
1104
|
log.info("No unregistered unshielded Night UTXOs available.");
|
|
981
|
-
|
|
982
|
-
|
|
1105
|
+
try {
|
|
1106
|
+
await waitForDustFunds(walletResult.wallet, {
|
|
1107
|
+
timeoutMs: 5000,
|
|
1108
|
+
waitNonZero: true,
|
|
1109
|
+
});
|
|
1110
|
+
return true;
|
|
1111
|
+
} catch {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
983
1114
|
}
|
|
984
1115
|
|
|
985
1116
|
log.info(`Found ${unregisteredNightUtxos.length} unregistered Night UTXOs. Registering for dust...`);
|
|
986
1117
|
|
|
987
1118
|
try {
|
|
988
|
-
const
|
|
1119
|
+
const { fee } = await walletResult.wallet.estimateRegistration(
|
|
1120
|
+
unregisteredNightUtxos,
|
|
1121
|
+
);
|
|
1122
|
+
log.info(`Waiting for the NIGHT UTXOs to generate the registration fee: ${fee}`);
|
|
1123
|
+
await walletResult.wallet.waitForGeneratedDust(
|
|
1124
|
+
unregisteredNightUtxos,
|
|
1125
|
+
fee,
|
|
1126
|
+
{ timeoutMs: resolveWalletSyncTimeoutMs() },
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
const recipe: UnprovenTransactionRecipe =
|
|
1130
|
+
await walletResult.wallet.registerNightUtxosForDustGeneration(
|
|
989
1131
|
unregisteredNightUtxos,
|
|
990
1132
|
walletResult.unshieldedKeystore.getPublicKey(),
|
|
991
|
-
(payload: Uint8Array) => walletResult.unshieldedKeystore.
|
|
1133
|
+
(payload: Uint8Array) => walletResult.unshieldedKeystore.signDataAsync(payload),
|
|
992
1134
|
);
|
|
1135
|
+
if (recipe.type !== "UNPROVEN_TRANSACTION") {
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
`Unexpected DUST registration recipe type: ${(recipe as { type?: unknown }).type}`,
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
993
1140
|
|
|
994
1141
|
log.info("Submitting dust registration transaction...");
|
|
995
1142
|
const txId = await walletResult.wallet.submitTransaction(
|
|
996
|
-
await
|
|
1143
|
+
await walletResult.wallet.finalizeRecipe(recipe),
|
|
997
1144
|
);
|
|
998
1145
|
log.info(`Dust registration submitted with tx id: ${txId}`);
|
|
999
1146
|
|
package/src/midnight-env.ts
CHANGED
|
@@ -18,7 +18,7 @@ const env = (key: string | string[], fallback?: string): string => {
|
|
|
18
18
|
throw new Error('Invalid key type');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
type NetworkConfig = {
|
|
21
|
+
export type NetworkConfig = {
|
|
22
22
|
indexer: string;
|
|
23
23
|
indexerWS: string;
|
|
24
24
|
node: string;
|
|
@@ -29,8 +29,8 @@ type NetworkConfig = {
|
|
|
29
29
|
|
|
30
30
|
// Midnight Network default configurations
|
|
31
31
|
const undeployedNetworkConfig: NetworkConfig = {
|
|
32
|
-
indexer: "http://127.0.0.1:8088/api/
|
|
33
|
-
indexerWS: "ws://127.0.0.1:8088/api/
|
|
32
|
+
indexer: "http://127.0.0.1:8088/api/v4/graphql",
|
|
33
|
+
indexerWS: "ws://127.0.0.1:8088/api/v4/graphql/ws",
|
|
34
34
|
node: "http://127.0.0.1:9944",
|
|
35
35
|
networkId: "undeployed" as NetworkId.NetworkId,
|
|
36
36
|
proofServer: "http://127.0.0.1:6300",
|
|
@@ -47,22 +47,20 @@ const deployedNetworkConfig = (networkId: NetworkId.NetworkId): NetworkConfig =>
|
|
|
47
47
|
genesisWalletSeed: '',
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve defaults for every wallet-SDK network ID. Only `undeployed` uses
|
|
52
|
+
* loopback services; every deployed or future network ID follows the hosted
|
|
53
|
+
* Midnight endpoint convention instead of being restricted to `stagenet`.
|
|
54
|
+
*/
|
|
55
|
+
export const defaultMidnightNetworkConfig = (
|
|
56
|
+
networkId: NetworkId.NetworkId,
|
|
57
|
+
): NetworkConfig =>
|
|
58
|
+
networkId === "undeployed"
|
|
59
|
+
? undeployedNetworkConfig
|
|
60
|
+
: deployedNetworkConfig(networkId);
|
|
61
|
+
|
|
62
|
+
const networkId = (env("MIDNIGHT_NETWORK_ID") || "undeployed") as NetworkId.NetworkId;
|
|
63
|
+
const selectedNetworkConfig = defaultMidnightNetworkConfig(networkId);
|
|
66
64
|
|
|
67
65
|
let walletSeed: string;
|
|
68
66
|
if (env("MIDNIGHT_WALLET_SEED")) {
|
package/src/mod.ts
CHANGED
|
@@ -10,6 +10,8 @@ export {
|
|
|
10
10
|
resolveWalletSyncTimeoutMs,
|
|
11
11
|
suspendAuxWalletSyncForFees,
|
|
12
12
|
resolveFacadeDustBalance,
|
|
13
|
+
resolveFacadeDustAvailableCoins,
|
|
14
|
+
resolveFacadeDustFundsReadiness,
|
|
13
15
|
safeStringifyProgress,
|
|
14
16
|
syncAndWaitForFunds,
|
|
15
17
|
buildWalletFacade,
|
|
@@ -26,7 +28,9 @@ export {
|
|
|
26
28
|
export { saveDustState, loadDustState } from "./dust-state.ts";
|
|
27
29
|
export type {
|
|
28
30
|
WalletSyncMode,
|
|
31
|
+
DustFundsReadiness,
|
|
29
32
|
DustSyncWithRetryOptions,
|
|
33
|
+
DustSyncWithRetryResult,
|
|
30
34
|
} from "./get-wallet-info.ts";
|
|
31
35
|
export { CONSTANTS } from "./constants.ts";
|
|
32
36
|
export {
|