@dynamic-labs-wallet/node-midnight 1.1.3 → 1.1.5

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/index.esm.js CHANGED
@@ -1,6 +1,13 @@
1
- import { createLogError, DynamicWalletClient, getMPCChainConfig } from '@dynamic-labs-wallet/node';
1
+ import { createHash } from 'node:crypto';
2
+ import { inspect } from 'node:util';
3
+ import { createLogError, DynamicWalletClient, getMPCChainConfig, WalletOperation } from '@dynamic-labs-wallet/node';
2
4
  import * as ledger from '@midnight-ntwrk/ledger-v8';
3
- import { MidnightBech32m, ShieldedAddress, ShieldedCoinPublicKey, ShieldedEncryptionPublicKey, DustAddress, mainnet } from '@midnight-ntwrk/wallet-sdk-address-format';
5
+ import { NoOpTransactionHistoryStorage } from '@midnight-ntwrk/wallet-sdk-abstractions';
6
+ import { MidnightBech32m, ShieldedAddress, ShieldedCoinPublicKey, ShieldedEncryptionPublicKey, DustAddress, mainnet, UnshieldedAddress } from '@midnight-ntwrk/wallet-sdk-address-format';
7
+ import { DustWallet } from '@midnight-ntwrk/wallet-sdk-dust-wallet';
8
+ import { WalletFacade } from '@midnight-ntwrk/wallet-sdk-facade';
9
+ import { ShieldedWallet } from '@midnight-ntwrk/wallet-sdk-shielded';
10
+ import { createKeystore, UnshieldedWallet, PublicKey } from '@midnight-ntwrk/wallet-sdk-unshielded-wallet';
4
11
  import { HDKey } from '@scure/bip32';
5
12
  import { bech32m } from 'bech32';
6
13
 
@@ -16,14 +23,64 @@ var MIDNIGHT_NETWORK_IDS = {
16
23
  preprod: 0,
17
24
  undeployed: 0
18
25
  };
26
+ var _process_env_MIDNIGHT_PROOF_SERVER_URL;
27
+ var MIDNIGHT_PROOF_SERVER_URL = (_process_env_MIDNIGHT_PROOF_SERVER_URL = process.env.MIDNIGHT_PROOF_SERVER_URL) !== null && _process_env_MIDNIGHT_PROOF_SERVER_URL !== void 0 ? _process_env_MIDNIGHT_PROOF_SERVER_URL : 'https://proof-server.us-east-1.dynamic-preprod.xyz';
28
+ /**
29
+ * Per-network wallet-facade infrastructure. There is no public mainnet proof
30
+ * server, so Dynamic's is shared across networks for now.
31
+ */ var MIDNIGHT_INFRA = {
32
+ mainnet: {
33
+ relayUrl: 'wss://rpc.mainnet.midnight.network',
34
+ provingServerUrl: MIDNIGHT_PROOF_SERVER_URL,
35
+ indexerHttpUrl: 'https://indexer.mainnet.midnight.network/api/v4/graphql',
36
+ indexerWsUrl: 'wss://indexer.mainnet.midnight.network/api/v4/graphql/ws'
37
+ },
38
+ preview: {
39
+ relayUrl: 'wss://rpc.preview.midnight.network',
40
+ provingServerUrl: MIDNIGHT_PROOF_SERVER_URL,
41
+ indexerHttpUrl: 'https://indexer.preview.midnight.network/api/v4/graphql',
42
+ indexerWsUrl: 'wss://indexer.preview.midnight.network/api/v4/graphql/ws'
43
+ }
44
+ };
19
45
  /**
20
46
  * Resolves a caller-supplied network identifier (numeric 0/1, network name, or
21
- * `midnight:<network>` chainId) to the facade network tag. Defaults to 'preview'.
47
+ * `midnight:<network>` chainId) to the facade network tag. Omitting it defaults
48
+ * to 'preview'.
49
+ *
50
+ * Throws on anything else rather than falling back, for the same reason
51
+ * {@link getNetworkIdFromChainId} does: an unrecognized identifier that quietly
52
+ * became 'preview' would have the caller sync one network while signing with
53
+ * another network's replay-protection ID, with no error to show for it.
22
54
  */ var resolveMidnightNetworkTag = function(networkId) {
23
55
  if (networkId === undefined || networkId === null) return 'preview';
24
- if (typeof networkId === 'number') return networkId === 1 ? 'mainnet' : 'preview';
56
+ if (typeof networkId === 'number') {
57
+ if (networkId === MIDNIGHT_NETWORK_IDS.mainnet) return 'mainnet';
58
+ if (networkId === MIDNIGHT_NETWORK_IDS.preview) return 'preview';
59
+ throw new Error("".concat(ERROR_UNKNOWN_NETWORK, ": ").concat(networkId));
60
+ }
25
61
  var name = networkId.includes(':') ? networkId.split(':')[1] : networkId;
26
- return name === 'mainnet' ? 'mainnet' : 'preview';
62
+ if (name === 'mainnet' || name === 'preview') return name;
63
+ throw new Error("".concat(ERROR_UNKNOWN_NETWORK, ": ").concat(networkId));
64
+ };
65
+ /**
66
+ * Resolves a network name (or `midnight:<network>` chainId) to its numeric
67
+ * network ID.
68
+ *
69
+ * Throws on unrecognized input rather than defaulting — silently falling back to
70
+ * mainnet would bake the wrong network ID into the transaction hash and break
71
+ * replay protection.
72
+ */ var getNetworkIdFromChainId = function(networkOrChainId) {
73
+ if (networkOrChainId in MIDNIGHT_NETWORK_IDS) {
74
+ return MIDNIGHT_NETWORK_IDS[networkOrChainId];
75
+ }
76
+ if (networkOrChainId.includes(':')) {
77
+ var network = networkOrChainId.split(':')[1];
78
+ if (network && network in MIDNIGHT_NETWORK_IDS) {
79
+ return MIDNIGHT_NETWORK_IDS[network];
80
+ }
81
+ }
82
+ var known = Object.keys(MIDNIGHT_NETWORK_IDS).join(', ');
83
+ throw new Error("".concat(ERROR_UNKNOWN_NETWORK, ': "').concat(networkOrChainId, '". Expected one of: ').concat(known, ', or "midnight:<network>".'));
27
84
  };
28
85
  var ERROR_KEYGEN_FAILED = 'Error with keygen';
29
86
  var ERROR_CREATE_WALLET_ACCOUNT = 'Error creating midnight wallet account';
@@ -31,6 +88,53 @@ var ERROR_IMPORT_PRIVATE_KEY = 'Error importing private key';
31
88
  var ERROR_EXPORT_PRIVATE_KEY = 'Error exporting private key';
32
89
  var ERROR_SIGN_MESSAGE = 'Error signing message';
33
90
  var ERROR_ACCOUNT_ADDRESS_REQUIRED = 'Account address is required';
91
+ var ERROR_SIGN_TRANSACTION = 'Error signing transaction';
92
+ var ERROR_UNKNOWN_NETWORK = 'Unknown Midnight network';
93
+ var ERROR_NETWORK_ID_REQUIRED = 'Network ID is required for Midnight transactions';
94
+ var ERROR_NO_DUST = 'No dust balance — register dust first';
95
+ var ERROR_SUBMIT_TRANSACTION = 'Error submitting transaction';
96
+ var ERROR_CREATE_TRANSFER = 'Error creating transfer transaction';
97
+ var ERROR_GET_BALANCE = 'Error fetching balance';
98
+ var ERROR_REGISTER_DUST = 'Error registering dust';
99
+ var ERROR_BALANCE_TRANSACTION = 'Error balancing contract transaction';
100
+
101
+ /** Whether a sub-wallet side has finished syncing. `highestRelevantIndex` is 0
102
+ * during sync, so `isStrictlyComplete()` is the authoritative signal. */ var isSideSynced = function(side) {
103
+ var _side_state;
104
+ var _side_progress;
105
+ var p = (_side_progress = side === null || side === void 0 ? void 0 : side.progress) !== null && _side_progress !== void 0 ? _side_progress : side === null || side === void 0 ? void 0 : (_side_state = side.state) === null || _side_state === void 0 ? void 0 : _side_state.progress;
106
+ if (!p) return false;
107
+ if (typeof p.isStrictlyComplete === 'function') return p.isStrictlyComplete();
108
+ var _p_appliedIndex;
109
+ var applied = BigInt((_p_appliedIndex = p.appliedIndex) !== null && _p_appliedIndex !== void 0 ? _p_appliedIndex : 0n);
110
+ var _p_highestRelevantIndex;
111
+ var highestRelevant = BigInt((_p_highestRelevantIndex = p.highestRelevantIndex) !== null && _p_highestRelevantIndex !== void 0 ? _p_highestRelevantIndex : 0n);
112
+ return highestRelevant > 0n && applied >= highestRelevant;
113
+ };
114
+ var isSyncResolved = function(state, mode) {
115
+ return mode === 'full' ? Boolean(state.isSynced) : isSideSynced(state.shielded) && isSideSynced(state.unshielded);
116
+ };
117
+ /**
118
+ * Whether a checkpoint may be restored into a facade for `network`.
119
+ *
120
+ * A checkpoint is only meaningful for the network it was taken on — sync
121
+ * indices do not carry across — so a mismatch is treated as no checkpoint at
122
+ * all rather than as an error, since the caller may legitimately hold state for
123
+ * several networks.
124
+ */ var isRestorableState = function(state, network) {
125
+ return state !== undefined && typeof state.shielded === 'string' && typeof state.dust === 'string' && state.network === network;
126
+ };
127
+ /**
128
+ * Whether the wallet is at a clean resting point, with nothing in flight.
129
+ *
130
+ * Checkpointing mid-flight would bake pending entries into whatever the caller
131
+ * stores and carry them forward on every later restore, so a dirty wallet is
132
+ * skipped and the caller simply keeps its previous checkpoint.
133
+ */ var isCleanForCheckpoint = function(state) {
134
+ var _state_pending_all, _state_pending, _state_unshielded_pendingCoins, _state_unshielded, _state_shielded_pendingCoins, _state_shielded, _state_dust_pendingCoins, _state_dust;
135
+ var _state_pending_all_length, _state_unshielded_pendingCoins_length, _state_shielded_pendingCoins_length, _state_dust_pendingCoins_length;
136
+ return ((_state_pending_all_length = state === null || state === void 0 ? void 0 : (_state_pending = state.pending) === null || _state_pending === void 0 ? void 0 : (_state_pending_all = _state_pending.all) === null || _state_pending_all === void 0 ? void 0 : _state_pending_all.length) !== null && _state_pending_all_length !== void 0 ? _state_pending_all_length : 0) === 0 && ((_state_unshielded_pendingCoins_length = state === null || state === void 0 ? void 0 : (_state_unshielded = state.unshielded) === null || _state_unshielded === void 0 ? void 0 : (_state_unshielded_pendingCoins = _state_unshielded.pendingCoins) === null || _state_unshielded_pendingCoins === void 0 ? void 0 : _state_unshielded_pendingCoins.length) !== null && _state_unshielded_pendingCoins_length !== void 0 ? _state_unshielded_pendingCoins_length : 0) === 0 && ((_state_shielded_pendingCoins_length = state === null || state === void 0 ? void 0 : (_state_shielded = state.shielded) === null || _state_shielded === void 0 ? void 0 : (_state_shielded_pendingCoins = _state_shielded.pendingCoins) === null || _state_shielded_pendingCoins === void 0 ? void 0 : _state_shielded_pendingCoins.length) !== null && _state_shielded_pendingCoins_length !== void 0 ? _state_shielded_pendingCoins_length : 0) === 0 && ((_state_dust_pendingCoins_length = state === null || state === void 0 ? void 0 : (_state_dust = state.dust) === null || _state_dust === void 0 ? void 0 : (_state_dust_pendingCoins = _state_dust.pendingCoins) === null || _state_dust_pendingCoins === void 0 ? void 0 : _state_dust_pendingCoins.length) !== null && _state_dust_pendingCoins_length !== void 0 ? _state_dust_pendingCoins_length : 0) === 0;
137
+ };
34
138
 
35
139
  function _array_like_to_array$1(arr, len) {
36
140
  if (len == null || len > arr.length) len = arr.length;
@@ -295,6 +399,40 @@ function _iterable_to_array_limit(arr, i) {
295
399
  function _non_iterable_rest() {
296
400
  throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
297
401
  }
402
+ function _object_spread(target) {
403
+ for(var i = 1; i < arguments.length; i++){
404
+ var source = arguments[i] != null ? arguments[i] : {};
405
+ var ownKeys = Object.keys(source);
406
+ if (typeof Object.getOwnPropertySymbols === "function") {
407
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
408
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
409
+ }));
410
+ }
411
+ ownKeys.forEach(function(key) {
412
+ _define_property(target, key, source[key]);
413
+ });
414
+ }
415
+ return target;
416
+ }
417
+ function ownKeys(object, enumerableOnly) {
418
+ var keys = Object.keys(object);
419
+ if (Object.getOwnPropertySymbols) {
420
+ var symbols = Object.getOwnPropertySymbols(object);
421
+ keys.push.apply(keys, symbols);
422
+ }
423
+ return keys;
424
+ }
425
+ function _object_spread_props(target, source) {
426
+ source = source != null ? source : {};
427
+ if (Object.getOwnPropertyDescriptors) {
428
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
429
+ } else {
430
+ ownKeys(Object(source)).forEach(function(key) {
431
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
432
+ });
433
+ }
434
+ return target;
435
+ }
298
436
  function _possible_constructor_return(self, call) {
299
437
  if (call && (_type_of(call) === "object" || typeof call === "function")) {
300
438
  return call;
@@ -426,10 +564,117 @@ function _ts_generator(thisArg, body) {
426
564
  };
427
565
  }
428
566
  }
567
+ function _ts_values(o) {
568
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
569
+ if (m) return m.call(o);
570
+ if (o && typeof o.length === "number") return {
571
+ next: function() {
572
+ if (o && i >= o.length) o = void 0;
573
+ return {
574
+ value: o && o[i++],
575
+ done: !o
576
+ };
577
+ }
578
+ };
579
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
580
+ }
429
581
  var logError = createLogError('node-midnight');
430
- // Unshielded is NON-hardened to match the server's MPC signing path (Sodot's
431
- // derivePrivateKeyFromXpriv is non-hardened-only). Shielded and dust are hardened Midnight-canonical paths; those roles are signed
432
- // entirely client-side, so the Sodot constraint does not apply.
582
+ /** Strict base64, optional padding validated before host input reaches the
583
+ * WASM deserializer, which accepts silently-truncated garbage otherwise. */ var BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;
584
+ /** Upper bound on a serialized transaction we will decode. Generous next to real
585
+ * transactions, small enough that a hostile payload is rejected before WASM. */ var MAX_SERIALIZED_TRANSACTION_BYTES = 4 * 1024 * 1024;
586
+ var SYNC_TIMEOUT_MS = 300000;
587
+ /** One retry is enough: the stale socket reports closed by the second attempt. */ var SUBMIT_ATTEMPTS = 2;
588
+ var SUBMIT_RETRY_DELAY_MS = 1000;
589
+ /**
590
+ * Whether an error is the node client's stale-connection failure rather than a
591
+ * ledger rejection.
592
+ *
593
+ * The websocket close reason sits several `cause` levels below the
594
+ * `SubmissionError` the facade surfaces, and Effect's `FiberFailure` exposes
595
+ * none of them as a readable `cause` property — the chain is only reachable
596
+ * through the rendered form, so that is what gets matched.
597
+ */ var isStaleConnectionError = function(error) {
598
+ return /disconnected from|WebSocket is not connected|Normal Closure/i.test(inspect(error, {
599
+ depth: 8
600
+ }));
601
+ };
602
+ /**
603
+ * Parses a caller-supplied transfer amount.
604
+ *
605
+ * `BigInt()` is too lenient to use directly here: `BigInt('')` is `0n`, so an
606
+ * empty amount would silently become a zero-value transfer, and `BigInt('-5')`
607
+ * yields a negative that trivially passes the balance check. Decimals do throw,
608
+ * but with an opaque message.
609
+ */ var parseTransferAmount = function(amount) {
610
+ if (typeof amount !== 'string' || !/^\d+$/.test(amount)) {
611
+ throw new Error("Invalid transfer amount ".concat(JSON.stringify(amount), ": expected a whole number in the token's smallest unit, as a string"));
612
+ }
613
+ var parsed = BigInt(amount);
614
+ if (parsed === 0n) {
615
+ throw new Error('Transfer amount must be greater than zero');
616
+ }
617
+ return parsed;
618
+ };
619
+ /**
620
+ * `submitTransaction` returns either a string txId or a TransactionIdentifier
621
+ * with `.toHex()`; normalize so callers always get a string.
622
+ */ var toTxHashString = function(txIdentifier) {
623
+ if (typeof txIdentifier === 'string') return txIdentifier;
624
+ var maybeHex = txIdentifier === null || txIdentifier === void 0 ? void 0 : txIdentifier.toHex;
625
+ if (typeof maybeHex === 'function') return txIdentifier.toHex();
626
+ return String(txIdentifier);
627
+ };
628
+ /**
629
+ * Validates caller-supplied base64 before it reaches the WASM deserializer.
630
+ * `Buffer.from(x, 'base64')` silently drops invalid characters, so a malformed
631
+ * payload would otherwise arrive as truncated-but-plausible bytes and fail deep
632
+ * inside WASM with an opaque error.
633
+ */ var decodeSerializedTransaction = function(transaction, label) {
634
+ if (!transaction || !BASE64_PATTERN.test(transaction)) {
635
+ throw new Error("".concat(label, ": transaction must be base64"));
636
+ }
637
+ var bytes = Buffer.from(transaction, 'base64');
638
+ if (bytes.length === 0 || bytes.length > MAX_SERIALIZED_TRANSACTION_BYTES) {
639
+ throw new Error("".concat(label, ": transaction must decode to 1..").concat(MAX_SERIALIZED_TRANSACTION_BYTES, " bytes, got ").concat(bytes.length));
640
+ }
641
+ return new Uint8Array(bytes);
642
+ };
643
+ /**
644
+ * Parses a caller-supplied ISO-8601 timestamp.
645
+ *
646
+ * `new Date()` alone is too lenient to enforce the contract: it accepts
647
+ * implementation-defined formats like `'March 5, 2026'`, and `'2026-13-45'`
648
+ * parses on some engines. A TTL misread by a few hours silently changes when
649
+ * the balanced transaction stops being valid, so the shape is checked first.
650
+ */ /**
651
+ * Anchored at both ends: `new Date()` also understands worded formats such as
652
+ * `'2026-09-01 1:00 PM'` and `'2026-09-01 12:00 EST'`, which are V8 extensions
653
+ * rather than ISO-8601 and parse differently — or not at all — elsewhere.
654
+ * Matching only a prefix would let those through.
655
+ *
656
+ * Spelled `\d\d` rather than `\d{2}` because each counted repetition adds to
657
+ * the regex complexity budget, and the nesting here already uses most of it.
658
+ */ var ISO_TIMESTAMP = /^(\d\d\d\d)-(\d\d)-(\d\d)([T ]\d\d:\d\d(:\d\d)?(\.\d+)?(Z|[+-]\d\d:?\d\d)?)?$/;
659
+ /** Whether the y/m/d the caller wrote is a date that exists. */ var isRealCalendarDate = function(year, month, day) {
660
+ var asUtc = new Date("".concat(year, "-").concat(month, "-").concat(day, "T00:00:00Z"));
661
+ return !Number.isNaN(asUtc.getTime()) && asUtc.getUTCDate() === Number(day);
662
+ };
663
+ var parseIsoTimestamp = function(value, label) {
664
+ // Date rolls impossible days forward instead of rejecting them, so
665
+ // '2026-02-29' becomes 1 March and the transaction outlives its stated ttl.
666
+ var match = ISO_TIMESTAMP.exec(value);
667
+ var parsed = match && isRealCalendarDate(match[1], match[2], match[3]) ? new Date(value) : new Date(Number.NaN);
668
+ if (Number.isNaN(parsed.getTime())) {
669
+ throw new TypeError("".concat(label, ": ttl must be a valid ISO-8601 date, got ").concat(JSON.stringify(value)));
670
+ }
671
+ return parsed;
672
+ };
673
+ // Unshielded is NON-hardened to match Sodot's derivePrivateKeyFromXpriv, which
674
+ // is non-hardened-only. The hardened Midnight-canonical path would make the
675
+ // wallet track a different address than the server signs for — no error, just
676
+ // silently diverging balances. Shielded and dust use the hardened canonical
677
+ // paths; those roles are signed client-side, so the constraint does not apply.
433
678
  var ROLE_DERIVATION_PATHS = {
434
679
  unshielded: 'm/44/2400/0/0/0',
435
680
  shielded: "m/44'/2400'/0'/3/0",
@@ -1022,6 +1267,1414 @@ var DynamicMidnightWalletClient = /*#__PURE__*/ function(DynamicWalletClient) {
1022
1267
  })();
1023
1268
  }
1024
1269
  },
1270
+ {
1271
+ key: "withWallet",
1272
+ value: /**
1273
+ * Boots a synced wallet, runs `use` against it, and stops it afterwards.
1274
+ *
1275
+ * Nothing is retained between calls: the wallet is created here and torn down
1276
+ * in `finally`, so the client holds no state of its own. Callers who want to
1277
+ * avoid the ~150s genesis sync pass back the checkpoint from a previous call
1278
+ * (`access.walletState`) and receive an updated one alongside every result.
1279
+ */ function withWallet(label, access, mode, use) {
1280
+ var _this = this;
1281
+ return _async_to_generator(function() {
1282
+ var network, booted, state, result, _ref, durable, session;
1283
+ return _ts_generator(this, function(_state) {
1284
+ switch(_state.label){
1285
+ case 0:
1286
+ network = resolveMidnightNetworkTag(access.networkId);
1287
+ return [
1288
+ 4,
1289
+ _this.bootWallet(label, access, network)
1290
+ ];
1291
+ case 1:
1292
+ booted = _state.sent();
1293
+ _state.label = 2;
1294
+ case 2:
1295
+ _state.trys.push([
1296
+ 2,
1297
+ ,
1298
+ 6,
1299
+ 10
1300
+ ]);
1301
+ return [
1302
+ 4,
1303
+ _this.waitForSync(booted.wallet, mode, label)
1304
+ ];
1305
+ case 3:
1306
+ state = _state.sent();
1307
+ return [
1308
+ 4,
1309
+ use(_object_spread_props(_object_spread({}, booted), {
1310
+ state: state
1311
+ }))
1312
+ ];
1313
+ case 4:
1314
+ result = _state.sent();
1315
+ return [
1316
+ 4,
1317
+ _this.checkpoint(booted.wallet, network, label)
1318
+ ];
1319
+ case 5:
1320
+ _ref = _state.sent(), durable = _ref.durable, session = _ref.session;
1321
+ return [
1322
+ 2,
1323
+ {
1324
+ result: result,
1325
+ walletState: durable,
1326
+ sessionState: session
1327
+ }
1328
+ ];
1329
+ case 6:
1330
+ _state.trys.push([
1331
+ 6,
1332
+ 8,
1333
+ ,
1334
+ 9
1335
+ ]);
1336
+ return [
1337
+ 4,
1338
+ booted.wallet.stop()
1339
+ ];
1340
+ case 7:
1341
+ _state.sent();
1342
+ return [
1343
+ 3,
1344
+ 9
1345
+ ];
1346
+ case 8:
1347
+ _state.sent();
1348
+ return [
1349
+ 3,
1350
+ 9
1351
+ ];
1352
+ case 9:
1353
+ return [
1354
+ 7
1355
+ ];
1356
+ case 10:
1357
+ return [
1358
+ 2
1359
+ ];
1360
+ }
1361
+ });
1362
+ })();
1363
+ }
1364
+ },
1365
+ {
1366
+ key: "checkpoint",
1367
+ value: /**
1368
+ * Serializes the wallet's shielded and dust state, or returns undefined when
1369
+ * the wallet has work in flight.
1370
+ *
1371
+ * Checkpointing a dirty wallet would carry pending entries forward into every
1372
+ * later restore, so the caller keeps its previous checkpoint instead — sync
1373
+ * reconciles against the chain either way.
1374
+ */ function checkpoint(wallet, network, label) {
1375
+ var _this = this;
1376
+ return _async_to_generator(function() {
1377
+ var current, _ref, shielded, dust, state, error;
1378
+ return _ts_generator(this, function(_state) {
1379
+ switch(_state.label){
1380
+ case 0:
1381
+ _state.trys.push([
1382
+ 0,
1383
+ 3,
1384
+ ,
1385
+ 4
1386
+ ]);
1387
+ return [
1388
+ 4,
1389
+ _this.readState(wallet)
1390
+ ];
1391
+ case 1:
1392
+ current = _state.sent();
1393
+ return [
1394
+ 4,
1395
+ Promise.all([
1396
+ wallet.shielded.serializeState(),
1397
+ wallet.dust.serializeState()
1398
+ ])
1399
+ ];
1400
+ case 2:
1401
+ _ref = _sliced_to_array.apply(void 0, [
1402
+ _state.sent(),
1403
+ 2
1404
+ ]), shielded = _ref[0], dust = _ref[1];
1405
+ state = {
1406
+ shielded: shielded,
1407
+ dust: dust,
1408
+ network: network,
1409
+ savedAt: Date.now()
1410
+ };
1411
+ // A dirty snapshot is still the truth about what this wallet has spent,
1412
+ // so it is kept for the session; it is only withheld from the caller,
1413
+ // who would otherwise carry pending entries forward on every restore.
1414
+ if (!isCleanForCheckpoint(current)) {
1415
+ _this.logger.info("[Midnight] ".concat(label, ": skipping checkpoint, wallet has work in flight"));
1416
+ return [
1417
+ 2,
1418
+ {
1419
+ session: state
1420
+ }
1421
+ ];
1422
+ }
1423
+ return [
1424
+ 2,
1425
+ {
1426
+ durable: state,
1427
+ session: state
1428
+ }
1429
+ ];
1430
+ case 3:
1431
+ error = _state.sent();
1432
+ // A checkpoint is an optimisation; failing to take one must not fail the
1433
+ // operation the caller actually asked for.
1434
+ _this.logger.warn("[Midnight] ".concat(label, ": could not take checkpoint"), error);
1435
+ return [
1436
+ 2,
1437
+ {}
1438
+ ];
1439
+ case 4:
1440
+ return [
1441
+ 2
1442
+ ];
1443
+ }
1444
+ });
1445
+ })();
1446
+ }
1447
+ },
1448
+ {
1449
+ key: "submitWithReconnect",
1450
+ value: /**
1451
+ * Submits, retrying once past a stale-connection failure.
1452
+ *
1453
+ * The facade's node client calls `api.disconnect()` during startup, and that
1454
+ * promise resolves before the socket has actually closed. A submit issued in
1455
+ * that window sees `isConnected === true`, skips reconnecting, and is then
1456
+ * rejected by the close that lands a moment later. The retry reconnects for
1457
+ * real, because by then the socket reports closed. Resubmitting is safe: the
1458
+ * bytes never reached the node, and an identical transaction has an identical
1459
+ * hash, so a duplicate would be rejected rather than spent twice.
1460
+ */ function submitWithReconnect(wallet, finalized) {
1461
+ var _this = this;
1462
+ return _async_to_generator(function() {
1463
+ var attempt, error;
1464
+ return _ts_generator(this, function(_state) {
1465
+ switch(_state.label){
1466
+ case 0:
1467
+ attempt = 1;
1468
+ _state.label = 1;
1469
+ case 1:
1470
+ _state.trys.push([
1471
+ 1,
1472
+ 3,
1473
+ ,
1474
+ 5
1475
+ ]);
1476
+ return [
1477
+ 4,
1478
+ wallet.submitTransaction(finalized)
1479
+ ];
1480
+ case 2:
1481
+ return [
1482
+ 2,
1483
+ _state.sent()
1484
+ ];
1485
+ case 3:
1486
+ error = _state.sent();
1487
+ if (attempt >= SUBMIT_ATTEMPTS || !isStaleConnectionError(error)) throw error;
1488
+ _this.logger.warn("[Midnight] submitTransaction: node connection was stale, reconnecting and retrying");
1489
+ return [
1490
+ 4,
1491
+ new Promise(function(resolve) {
1492
+ return setTimeout(resolve, SUBMIT_RETRY_DELAY_MS);
1493
+ })
1494
+ ];
1495
+ case 4:
1496
+ _state.sent();
1497
+ return [
1498
+ 3,
1499
+ 5
1500
+ ];
1501
+ case 5:
1502
+ attempt++;
1503
+ return [
1504
+ 3,
1505
+ 1
1506
+ ];
1507
+ case 6:
1508
+ return [
1509
+ 2
1510
+ ];
1511
+ }
1512
+ });
1513
+ })();
1514
+ }
1515
+ },
1516
+ {
1517
+ key: "readState",
1518
+ value: /** First emission from the wallet's state observable. */ function readState(wallet) {
1519
+ return _async_to_generator(function() {
1520
+ return _ts_generator(this, function(_state) {
1521
+ return [
1522
+ 2,
1523
+ new Promise(function(resolve, reject) {
1524
+ // eslint-disable-next-line prefer-const
1525
+ var sub;
1526
+ var timeout = setTimeout(function() {
1527
+ sub === null || sub === void 0 ? void 0 : sub.unsubscribe();
1528
+ reject(new Error('timed out reading wallet state'));
1529
+ }, 10000);
1530
+ sub = wallet.state().subscribe(function(state) {
1531
+ clearTimeout(timeout);
1532
+ queueMicrotask(function() {
1533
+ return sub === null || sub === void 0 ? void 0 : sub.unsubscribe();
1534
+ });
1535
+ resolve(state);
1536
+ });
1537
+ })
1538
+ ];
1539
+ });
1540
+ })();
1541
+ }
1542
+ },
1543
+ {
1544
+ key: "bootWallet",
1545
+ value: function bootWallet(label, access, network) {
1546
+ var _this = this;
1547
+ return _async_to_generator(function() {
1548
+ var roleKeys, shieldedSecretKeys, dustSecretKey, unshieldedKeystore, checkpoint, infra, wallet;
1549
+ return _ts_generator(this, function(_state) {
1550
+ switch(_state.label){
1551
+ case 0:
1552
+ return [
1553
+ 4,
1554
+ _this.getRoleKeys(access)
1555
+ ];
1556
+ case 1:
1557
+ roleKeys = _state.sent();
1558
+ shieldedSecretKeys = ledger.ZswapSecretKeys.fromSeed(new Uint8Array(Buffer.from(roleKeys.shielded, 'hex')));
1559
+ dustSecretKey = ledger.DustSecretKey.fromSeed(new Uint8Array(Buffer.from(roleKeys.dust, 'hex')));
1560
+ unshieldedKeystore = createKeystore(new Uint8Array(Buffer.from(roleKeys.unshielded, 'hex')), network);
1561
+ // A checkpoint from another network would corrupt sync, so a mismatch is
1562
+ // treated as absent rather than as an error — callers may hold several.
1563
+ checkpoint = isRestorableState(access.walletState, network) ? access.walletState : undefined;
1564
+ infra = MIDNIGHT_INFRA[network];
1565
+ _this.logger.info("[Midnight] ".concat(label, ": initializing wallet facade (").concat(network, ", ").concat(checkpoint ? "resuming from checkpoint saved ".concat(new Date(checkpoint.savedAt).toISOString()) : 'syncing from genesis', ")"));
1566
+ return [
1567
+ 4,
1568
+ WalletFacade.init({
1569
+ configuration: {
1570
+ networkId: network,
1571
+ costParameters: {
1572
+ feeBlocksMargin: 5
1573
+ },
1574
+ relayURL: new URL(infra.relayUrl),
1575
+ provingServerUrl: new URL(infra.provingServerUrl),
1576
+ indexerClientConnection: {
1577
+ indexerHttpUrl: infra.indexerHttpUrl,
1578
+ indexerWsUrl: infra.indexerWsUrl
1579
+ },
1580
+ // Measured: 2000 and 20000 both sync in ~150s — the fold is apply-bound
1581
+ // (per-entry WASM compute), not fetch-bound, so batch size is not a lever.
1582
+ batchUpdates: {
1583
+ size: 2000
1584
+ },
1585
+ txHistoryStorage: new NoOpTransactionHistoryStorage()
1586
+ },
1587
+ shielded: function(cfg) {
1588
+ return checkpoint ? ShieldedWallet(cfg).restore(checkpoint.shielded) : ShieldedWallet(cfg).startWithSecretKeys(shieldedSecretKeys);
1589
+ },
1590
+ unshielded: function(cfg) {
1591
+ return UnshieldedWallet(cfg).startWithPublicKey(PublicKey.fromKeyStore(unshieldedKeystore));
1592
+ },
1593
+ dust: function(cfg) {
1594
+ return checkpoint ? DustWallet(cfg).restore(checkpoint.dust) : DustWallet(cfg).startWithSecretKey(dustSecretKey, ledger.LedgerParameters.initialParameters().dust);
1595
+ }
1596
+ })
1597
+ ];
1598
+ case 2:
1599
+ wallet = _state.sent();
1600
+ return [
1601
+ 4,
1602
+ wallet.start(shieldedSecretKeys, dustSecretKey)
1603
+ ];
1604
+ case 3:
1605
+ _state.sent();
1606
+ return [
1607
+ 2,
1608
+ {
1609
+ wallet: wallet,
1610
+ roleKeys: roleKeys,
1611
+ shieldedSecretKeys: shieldedSecretKeys,
1612
+ dustSecretKey: dustSecretKey
1613
+ }
1614
+ ];
1615
+ }
1616
+ });
1617
+ })();
1618
+ }
1619
+ },
1620
+ {
1621
+ key: "waitForSync",
1622
+ value: /**
1623
+ * Resolves once the requested sides report synced.
1624
+ *
1625
+ * Does not stop the wallet on failure — `withWallet` owns the wallet's
1626
+ * lifetime and tears it down in `finally`, whether this resolves or throws.
1627
+ */ function waitForSync(wallet, mode, label) {
1628
+ var _this = this;
1629
+ return _async_to_generator(function() {
1630
+ var startedAt, lastLoggedAt;
1631
+ return _ts_generator(this, function(_state) {
1632
+ switch(_state.label){
1633
+ case 0:
1634
+ startedAt = Date.now();
1635
+ lastLoggedAt = 0;
1636
+ return [
1637
+ 4,
1638
+ new Promise(function(resolve, reject) {
1639
+ // `let` + optional chaining: an already-synced wallet emits synchronously
1640
+ // during .subscribe(), so the callback can touch `sub` before assignment.
1641
+ // eslint-disable-next-line prefer-const
1642
+ var sub;
1643
+ var timeout = setTimeout(function() {
1644
+ sub === null || sub === void 0 ? void 0 : sub.unsubscribe();
1645
+ reject(new Error("[Midnight] ".concat(label, ": wallet sync timeout after ").concat(SYNC_TIMEOUT_MS, "ms")));
1646
+ }, SYNC_TIMEOUT_MS);
1647
+ sub = wallet.state().subscribe(function(state) {
1648
+ var now = Date.now();
1649
+ if (now - lastLoggedAt > 15000) {
1650
+ lastLoggedAt = now;
1651
+ _this.logger.info("[Midnight] ".concat(label, ": syncing ").concat(((now - startedAt) / 1000).toFixed(0), "s \xb7 ") + "shielded=".concat(isSideSynced(state.shielded), " unshielded=").concat(isSideSynced(state.unshielded), " ") + "dust=".concat(isSideSynced(state.dust)));
1652
+ }
1653
+ if (isSyncResolved(state, mode)) {
1654
+ clearTimeout(timeout);
1655
+ queueMicrotask(function() {
1656
+ return sub === null || sub === void 0 ? void 0 : sub.unsubscribe();
1657
+ });
1658
+ _this.logger.info("[Midnight] ".concat(label, ": synced in ").concat(((now - startedAt) / 1000).toFixed(1), "s (").concat(mode, ")"));
1659
+ resolve(state);
1660
+ }
1661
+ });
1662
+ })
1663
+ ];
1664
+ case 1:
1665
+ return [
1666
+ 2,
1667
+ _state.sent()
1668
+ ];
1669
+ }
1670
+ });
1671
+ })();
1672
+ }
1673
+ },
1674
+ {
1675
+ key: "getBalances",
1676
+ value: /**
1677
+ * Reads unshielded, shielded, and dust balances.
1678
+ *
1679
+ * Resolves as soon as shielded + unshielded are synced, leaving dust folding in
1680
+ * the background — `dustSynced` says whether the dust figure is final yet.
1681
+ */ function getBalances(access) {
1682
+ var _this = this;
1683
+ return _async_to_generator(function() {
1684
+ var _ref, result, walletState, error;
1685
+ return _ts_generator(this, function(_state) {
1686
+ switch(_state.label){
1687
+ case 0:
1688
+ _state.trys.push([
1689
+ 0,
1690
+ 2,
1691
+ ,
1692
+ 3
1693
+ ]);
1694
+ return [
1695
+ 4,
1696
+ _this.withWallet('getBalances', access, 'shielded-unshielded', /*#__PURE__*/ function() {
1697
+ var _ref = _async_to_generator(function(param) {
1698
+ var state, _state_dust_balance, _state_dust, _state_dust1, _state_unshielded, _state_shielded, toStringMap, now, _state_dust_balance1, dustBalance, _state_dust_totalCoins, dustCap;
1699
+ return _ts_generator(this, function(_state) {
1700
+ state = param.state;
1701
+ toStringMap = function() {
1702
+ var balances = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
1703
+ return Object.fromEntries(Object.entries(balances).map(function(param) {
1704
+ var _param = _sliced_to_array(param, 2), token = _param[0], value = _param[1];
1705
+ return [
1706
+ token,
1707
+ value.toString()
1708
+ ];
1709
+ }));
1710
+ };
1711
+ now = new Date();
1712
+ dustBalance = (_state_dust_balance1 = (_state_dust = state.dust) === null || _state_dust === void 0 ? void 0 : (_state_dust_balance = _state_dust.balance) === null || _state_dust_balance === void 0 ? void 0 : _state_dust_balance.call(_state_dust, now)) !== null && _state_dust_balance1 !== void 0 ? _state_dust_balance1 : 0n;
1713
+ // v4 exposes the generation cap per coin; the wallet-level cap is their sum.
1714
+ dustCap = ((_state_dust_totalCoins = (_state_dust1 = state.dust) === null || _state_dust1 === void 0 ? void 0 : _state_dust1.totalCoins) !== null && _state_dust_totalCoins !== void 0 ? _state_dust_totalCoins : []).reduce(function(sum, coin) {
1715
+ var _coin_maxCap;
1716
+ return sum + ((_coin_maxCap = coin.maxCap) !== null && _coin_maxCap !== void 0 ? _coin_maxCap : 0n);
1717
+ }, 0n);
1718
+ return [
1719
+ 2,
1720
+ {
1721
+ unshielded: toStringMap((_state_unshielded = state.unshielded) === null || _state_unshielded === void 0 ? void 0 : _state_unshielded.balances),
1722
+ shielded: toStringMap((_state_shielded = state.shielded) === null || _state_shielded === void 0 ? void 0 : _state_shielded.balances),
1723
+ dust: {
1724
+ balance: dustBalance.toString(),
1725
+ cap: dustCap.toString()
1726
+ },
1727
+ dustSynced: isSideSynced(state.dust),
1728
+ address: access.walletMetadata.accountAddress
1729
+ }
1730
+ ];
1731
+ });
1732
+ });
1733
+ return function(_) {
1734
+ return _ref.apply(this, arguments);
1735
+ };
1736
+ }())
1737
+ ];
1738
+ case 1:
1739
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState;
1740
+ return [
1741
+ 2,
1742
+ _object_spread_props(_object_spread({}, result), {
1743
+ walletState: walletState
1744
+ })
1745
+ ];
1746
+ case 2:
1747
+ error = _state.sent();
1748
+ logError({
1749
+ message: ERROR_GET_BALANCE,
1750
+ error: error,
1751
+ context: {
1752
+ accountAddress: access.walletMetadata.accountAddress
1753
+ }
1754
+ });
1755
+ throw _instanceof(error, Error) ? error : new Error(ERROR_GET_BALANCE);
1756
+ case 3:
1757
+ return [
1758
+ 2
1759
+ ];
1760
+ }
1761
+ });
1762
+ })();
1763
+ }
1764
+ },
1765
+ {
1766
+ key: "registerDust",
1767
+ value: /**
1768
+ * Registers the wallet's NIGHT UTXOs for dust generation. Dust pays fees on
1769
+ * every Midnight transaction, so this is a prerequisite for transacting.
1770
+ *
1771
+ * Returns immediately after broadcast — dust accrues continuously on-chain and
1772
+ * can take minutes, so poll {@link getBalances} rather than blocking here.
1773
+ */ function registerDust(access) {
1774
+ var _this = this;
1775
+ return _async_to_generator(function() {
1776
+ var _ref, result, walletState, error;
1777
+ return _ts_generator(this, function(_state) {
1778
+ switch(_state.label){
1779
+ case 0:
1780
+ _state.trys.push([
1781
+ 0,
1782
+ 2,
1783
+ ,
1784
+ 3
1785
+ ]);
1786
+ return [
1787
+ 4,
1788
+ _this.withWallet('registerDust', access, 'full', /*#__PURE__*/ function() {
1789
+ var _ref = _async_to_generator(function(param) {
1790
+ var wallet, roleKeys, state, _state_dust_balance, _state_dust, _state_unshielded, _state_dust_balance1, existingDust, _state_unshielded_availableCoins, allUtxos, unregistered, registered, verifyingKey, recipe, finalized, txId;
1791
+ return _ts_generator(this, function(_state) {
1792
+ switch(_state.label){
1793
+ case 0:
1794
+ wallet = param.wallet, roleKeys = param.roleKeys, state = param.state;
1795
+ existingDust = (_state_dust_balance1 = (_state_dust = state.dust) === null || _state_dust === void 0 ? void 0 : (_state_dust_balance = _state_dust.balance) === null || _state_dust_balance === void 0 ? void 0 : _state_dust_balance.call(_state_dust, new Date())) !== null && _state_dust_balance1 !== void 0 ? _state_dust_balance1 : 0n;
1796
+ if (existingDust > 0n) {
1797
+ return [
1798
+ 2,
1799
+ {
1800
+ status: 'already_has_dust',
1801
+ message: "Wallet already has ".concat(existingDust, " dust; no action needed."),
1802
+ registeredCount: 0,
1803
+ dustBalance: existingDust.toString()
1804
+ }
1805
+ ];
1806
+ }
1807
+ // The flag lives under `.meta`, not on the coin itself — reading it one
1808
+ // level up is silently always undefined, which makes every UTXO look
1809
+ // unregistered and re-submits a registration tx on every call. Matches
1810
+ // packages/midnight (browser), which reads the same shape.
1811
+ allUtxos = (_state_unshielded_availableCoins = (_state_unshielded = state.unshielded) === null || _state_unshielded === void 0 ? void 0 : _state_unshielded.availableCoins) !== null && _state_unshielded_availableCoins !== void 0 ? _state_unshielded_availableCoins : [];
1812
+ unregistered = allUtxos.filter(function(utxo) {
1813
+ var _utxo_meta;
1814
+ return ((_utxo_meta = utxo.meta) === null || _utxo_meta === void 0 ? void 0 : _utxo_meta.registeredForDustGeneration) !== true;
1815
+ });
1816
+ if (unregistered.length === 0) {
1817
+ registered = allUtxos.length > 0;
1818
+ return [
1819
+ 2,
1820
+ {
1821
+ status: registered ? 'already_registered' : 'no_utxos',
1822
+ message: registered ? "All ".concat(allUtxos.length, " NIGHT UTXO(s) are already registered. Dust accrues on-chain — poll getBalances.") : 'No NIGHT UTXOs. Fund the wallet with NIGHT before registering for dust.',
1823
+ registeredCount: 0,
1824
+ dustBalance: '0'
1825
+ }
1826
+ ];
1827
+ }
1828
+ verifyingKey = ledger.signatureVerifyingKey(roleKeys.unshielded);
1829
+ return [
1830
+ 4,
1831
+ wallet.registerNightUtxosForDustGeneration(unregistered, verifyingKey, function(payload) {
1832
+ return ledger.signData(roleKeys.unshielded, payload);
1833
+ })
1834
+ ];
1835
+ case 1:
1836
+ recipe = _state.sent();
1837
+ return [
1838
+ 4,
1839
+ wallet.finalizeRecipe(recipe)
1840
+ ];
1841
+ case 2:
1842
+ finalized = _state.sent();
1843
+ return [
1844
+ 4,
1845
+ wallet.submitTransaction(finalized)
1846
+ ];
1847
+ case 3:
1848
+ txId = toTxHashString.apply(void 0, [
1849
+ _state.sent()
1850
+ ]);
1851
+ return [
1852
+ 2,
1853
+ {
1854
+ status: 'registered',
1855
+ message: "Registered ".concat(unregistered.length, " NIGHT UTXO(s) for dust generation (tx ").concat(txId.slice(0, 16), "…). ") + 'Dust accrues continuously — poll getBalances to see it arrive.',
1856
+ registeredCount: unregistered.length,
1857
+ dustBalance: '0',
1858
+ txId: txId
1859
+ }
1860
+ ];
1861
+ }
1862
+ });
1863
+ });
1864
+ return function(_) {
1865
+ return _ref.apply(this, arguments);
1866
+ };
1867
+ }())
1868
+ ];
1869
+ case 1:
1870
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState;
1871
+ return [
1872
+ 2,
1873
+ _object_spread_props(_object_spread({}, result), {
1874
+ walletState: walletState
1875
+ })
1876
+ ];
1877
+ case 2:
1878
+ error = _state.sent();
1879
+ logError({
1880
+ message: ERROR_REGISTER_DUST,
1881
+ error: error,
1882
+ context: {
1883
+ accountAddress: access.walletMetadata.accountAddress
1884
+ }
1885
+ });
1886
+ throw _instanceof(error, Error) ? error : new Error(ERROR_REGISTER_DUST);
1887
+ case 3:
1888
+ return [
1889
+ 2
1890
+ ];
1891
+ }
1892
+ });
1893
+ })();
1894
+ }
1895
+ },
1896
+ {
1897
+ key: "createTransferTransaction",
1898
+ value: /**
1899
+ * Builds an unsigned transfer. No MPC, no proving, no broadcast — pair with
1900
+ * {@link signTransaction} then {@link submitTransaction}.
1901
+ */ function createTransferTransaction(access) {
1902
+ var _this = this;
1903
+ return _async_to_generator(function() {
1904
+ var transfers, _access_ttlMinutes, ttlMinutes, _ref, result, walletState, error;
1905
+ return _ts_generator(this, function(_state) {
1906
+ switch(_state.label){
1907
+ case 0:
1908
+ transfers = access.transfers, _access_ttlMinutes = access.ttlMinutes, ttlMinutes = _access_ttlMinutes === void 0 ? 5 : _access_ttlMinutes;
1909
+ if (!(transfers === null || transfers === void 0 ? void 0 : transfers.length)) {
1910
+ throw new Error('At least one transfer is required');
1911
+ }
1912
+ _state.label = 1;
1913
+ case 1:
1914
+ _state.trys.push([
1915
+ 1,
1916
+ 3,
1917
+ ,
1918
+ 4
1919
+ ]);
1920
+ return [
1921
+ 4,
1922
+ _this.withWallet('createTransferTransaction', access, 'full', /*#__PURE__*/ function() {
1923
+ var _ref = _async_to_generator(function(param) {
1924
+ var wallet, state, shieldedSecretKeys, dustSecretKey, _state_dust_balance, _state_dust, _state_dust_balance1, nativeToken, network, requiredByKey, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, transfer, _transfer_tokenType, token, key, existing, _existing_required, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, side, token1, required, _state_unshielded, _state_shielded, balances, _balances_token, have, sdkTransfers, recipe;
1925
+ return _ts_generator(this, function(_state) {
1926
+ switch(_state.label){
1927
+ case 0:
1928
+ wallet = param.wallet, state = param.state, shieldedSecretKeys = param.shieldedSecretKeys, dustSecretKey = param.dustSecretKey;
1929
+ // Dust pays the fee on every Midnight transaction regardless of transfer type.
1930
+ if (((_state_dust_balance1 = (_state_dust = state.dust) === null || _state_dust === void 0 ? void 0 : (_state_dust_balance = _state_dust.balance) === null || _state_dust_balance === void 0 ? void 0 : _state_dust_balance.call(_state_dust, new Date())) !== null && _state_dust_balance1 !== void 0 ? _state_dust_balance1 : 0n) === 0n) {
1931
+ throw new Error(ERROR_NO_DUST);
1932
+ }
1933
+ nativeToken = ledger.nativeToken().raw;
1934
+ network = resolveMidnightNetworkTag(access.networkId);
1935
+ // Aggregate per (side, token) so multiple outputs spending the same token
1936
+ // are checked against one combined total. NUL separator: it cannot appear
1937
+ // in any Midnight address or token encoding, so keys never collide (a
1938
+ // token identifier may itself contain ':').
1939
+ requiredByKey = new Map();
1940
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1941
+ try {
1942
+ for(_iterator = transfers[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1943
+ transfer = _step.value;
1944
+ ;
1945
+ token = (_transfer_tokenType = transfer.tokenType) !== null && _transfer_tokenType !== void 0 ? _transfer_tokenType : nativeToken;
1946
+ key = "".concat(transfer.type, "\0").concat(token);
1947
+ existing = requiredByKey.get(key);
1948
+ ;
1949
+ requiredByKey.set(key, {
1950
+ side: transfer.type,
1951
+ token: token,
1952
+ required: ((_existing_required = existing === null || existing === void 0 ? void 0 : existing.required) !== null && _existing_required !== void 0 ? _existing_required : 0n) + parseTransferAmount(transfer.amount)
1953
+ });
1954
+ }
1955
+ } catch (err) {
1956
+ _didIteratorError = true;
1957
+ _iteratorError = err;
1958
+ } finally{
1959
+ try {
1960
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1961
+ _iterator.return();
1962
+ }
1963
+ } finally{
1964
+ if (_didIteratorError) {
1965
+ throw _iteratorError;
1966
+ }
1967
+ }
1968
+ }
1969
+ _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
1970
+ try {
1971
+ for(_iterator1 = requiredByKey.values()[Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
1972
+ _step_value = _step1.value, side = _step_value.side, token1 = _step_value.token, required = _step_value.required;
1973
+ ;
1974
+ balances = side === 'unshielded' ? (_state_unshielded = state.unshielded) === null || _state_unshielded === void 0 ? void 0 : _state_unshielded.balances : (_state_shielded = state.shielded) === null || _state_shielded === void 0 ? void 0 : _state_shielded.balances;
1975
+ ;
1976
+ have = (_balances_token = balances === null || balances === void 0 ? void 0 : balances[token1]) !== null && _balances_token !== void 0 ? _balances_token : 0n;
1977
+ if (have < required) {
1978
+ throw new Error("Insufficient ".concat(side, " balance for token ").concat(token1, ": ").concat(have, " < ").concat(required));
1979
+ }
1980
+ }
1981
+ } catch (err) {
1982
+ _didIteratorError1 = true;
1983
+ _iteratorError1 = err;
1984
+ } finally{
1985
+ try {
1986
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
1987
+ _iterator1.return();
1988
+ }
1989
+ } finally{
1990
+ if (_didIteratorError1) {
1991
+ throw _iteratorError1;
1992
+ }
1993
+ }
1994
+ }
1995
+ sdkTransfers = transfers.map(function(transfer) {
1996
+ var _transfer_tokenType;
1997
+ var token = (_transfer_tokenType = transfer.tokenType) !== null && _transfer_tokenType !== void 0 ? _transfer_tokenType : nativeToken;
1998
+ // The SDK wants a decoded address object, not the bech32m string.
1999
+ var receiverAddress;
2000
+ try {
2001
+ var parsed = MidnightBech32m.parse(transfer.recipientAddress);
2002
+ receiverAddress = transfer.type === 'unshielded' ? parsed.decode(UnshieldedAddress, network) : parsed.decode(ShieldedAddress, network);
2003
+ } catch (err) {
2004
+ throw new Error("Invalid ".concat(transfer.type, " recipient address ").concat(transfer.recipientAddress, ": ").concat(err.message));
2005
+ }
2006
+ return {
2007
+ type: transfer.type,
2008
+ outputs: [
2009
+ {
2010
+ amount: parseTransferAmount(transfer.amount),
2011
+ receiverAddress: receiverAddress,
2012
+ type: token
2013
+ }
2014
+ ]
2015
+ };
2016
+ });
2017
+ return [
2018
+ 4,
2019
+ wallet.transferTransaction(sdkTransfers, {
2020
+ shieldedSecretKeys: shieldedSecretKeys,
2021
+ dustSecretKey: dustSecretKey
2022
+ }, {
2023
+ ttl: new Date(Date.now() + ttlMinutes * 60 * 1000)
2024
+ })
2025
+ ];
2026
+ case 1:
2027
+ recipe = _state.sent();
2028
+ if (recipe.type !== 'UNPROVEN_TRANSACTION') {
2029
+ throw new Error("Unexpected recipe type from transferTransaction: ".concat(recipe.type));
2030
+ }
2031
+ return [
2032
+ 2,
2033
+ {
2034
+ serializedTransaction: Buffer.from(recipe.transaction.serialize()).toString('base64')
2035
+ }
2036
+ ];
2037
+ }
2038
+ });
2039
+ });
2040
+ return function(_) {
2041
+ return _ref.apply(this, arguments);
2042
+ };
2043
+ }())
2044
+ ];
2045
+ case 2:
2046
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState;
2047
+ return [
2048
+ 2,
2049
+ _object_spread_props(_object_spread({}, result), {
2050
+ walletState: walletState
2051
+ })
2052
+ ];
2053
+ case 3:
2054
+ error = _state.sent();
2055
+ logError({
2056
+ message: ERROR_CREATE_TRANSFER,
2057
+ error: error,
2058
+ context: {
2059
+ accountAddress: access.walletMetadata.accountAddress
2060
+ }
2061
+ });
2062
+ throw _instanceof(error, Error) ? error : new Error(ERROR_CREATE_TRANSFER);
2063
+ case 4:
2064
+ return [
2065
+ 2
2066
+ ];
2067
+ }
2068
+ });
2069
+ })();
2070
+ }
2071
+ },
2072
+ {
2073
+ key: "submitTransaction",
2074
+ value: /**
2075
+ * Broadcasts a finalized transaction. Pure RPC — reuses the cached wallet's
2076
+ * node connection; no MPC and no proving.
2077
+ *
2078
+ * Returns both identifiers, because they are not interchangeable:
2079
+ * - `txHash` is the canonical ledger hash, and what the block explorer indexes.
2080
+ * - `txIdentifier` is the submission handle; midnight-js polls
2081
+ * `transactions(offset: { identifier })` to observe inclusion, so a caller
2082
+ * handed only the hash would wait forever on a transaction that succeeded.
2083
+ */ function submitTransaction(access) {
2084
+ var _this = this;
2085
+ return _async_to_generator(function() {
2086
+ var bytes, _ref, result, walletState, error;
2087
+ return _ts_generator(this, function(_state) {
2088
+ switch(_state.label){
2089
+ case 0:
2090
+ // Validate before booting the facade — see signTransaction.
2091
+ bytes = decodeSerializedTransaction(access.transaction, 'submitTransaction');
2092
+ _state.label = 1;
2093
+ case 1:
2094
+ _state.trys.push([
2095
+ 1,
2096
+ 3,
2097
+ ,
2098
+ 4
2099
+ ]);
2100
+ return [
2101
+ 4,
2102
+ _this.withWallet('submitTransaction', access, 'full', /*#__PURE__*/ function() {
2103
+ var _ref = _async_to_generator(function(param) {
2104
+ var wallet, finalized, txIdentifier, txHash, canonical;
2105
+ return _ts_generator(this, function(_state) {
2106
+ switch(_state.label){
2107
+ case 0:
2108
+ wallet = param.wallet;
2109
+ finalized = ledger.Transaction.deserialize('signature', 'proof', 'binding', bytes);
2110
+ return [
2111
+ 4,
2112
+ _this.submitWithReconnect(wallet, finalized)
2113
+ ];
2114
+ case 1:
2115
+ txIdentifier = toTxHashString.apply(void 0, [
2116
+ _state.sent()
2117
+ ]);
2118
+ txHash = txIdentifier;
2119
+ try {
2120
+ canonical = toTxHashString(finalized.transactionHash());
2121
+ if (canonical) txHash = canonical;
2122
+ } catch (err) {
2123
+ _this.logger.warn('[Midnight] submitTransaction: could not derive canonical hash, using identifier', err);
2124
+ }
2125
+ return [
2126
+ 2,
2127
+ {
2128
+ txHash: txHash,
2129
+ txIdentifier: txIdentifier
2130
+ }
2131
+ ];
2132
+ }
2133
+ });
2134
+ });
2135
+ return function(_) {
2136
+ return _ref.apply(this, arguments);
2137
+ };
2138
+ }())
2139
+ ];
2140
+ case 2:
2141
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState;
2142
+ return [
2143
+ 2,
2144
+ _object_spread_props(_object_spread({}, result), {
2145
+ walletState: walletState
2146
+ })
2147
+ ];
2148
+ case 3:
2149
+ error = _state.sent();
2150
+ logError({
2151
+ message: ERROR_SUBMIT_TRANSACTION,
2152
+ error: error,
2153
+ context: {
2154
+ accountAddress: access.walletMetadata.accountAddress
2155
+ }
2156
+ });
2157
+ throw _instanceof(error, Error) ? error : new Error(ERROR_SUBMIT_TRANSACTION);
2158
+ case 4:
2159
+ return [
2160
+ 2
2161
+ ];
2162
+ }
2163
+ });
2164
+ })();
2165
+ }
2166
+ },
2167
+ {
2168
+ key: "signTransaction",
2169
+ value: /**
2170
+ * MPC-signs every unshielded intent segment, then finalizes (ZK-proves) the
2171
+ * transaction through the proof server.
2172
+ *
2173
+ * Side effect: finalizeRecipe mutates wallet state by adding a pending-tx
2174
+ * entry. If the caller never submits, that entry lingers until the next
2175
+ * re-sync reconciles against the chain.
2176
+ */ function signTransaction(access) {
2177
+ var _this = this;
2178
+ return _async_to_generator(function() {
2179
+ var transaction, networkId, context, resolvedNetworkId, bytes, _ref, result, walletState, error;
2180
+ return _ts_generator(this, function(_state) {
2181
+ switch(_state.label){
2182
+ case 0:
2183
+ transaction = access.transaction, networkId = access.networkId, context = access.context;
2184
+ if (networkId === undefined) {
2185
+ // Never default: the network id is baked into the transaction hash for
2186
+ // replay protection, so guessing wrong is a correctness bug, not a nit.
2187
+ throw new Error(ERROR_NETWORK_ID_REQUIRED);
2188
+ }
2189
+ resolvedNetworkId = typeof networkId === 'number' ? networkId : getNetworkIdFromChainId(networkId);
2190
+ // Validate before booting the facade: a cold sync is ~150s and getRoleKeys
2191
+ // runs a full MPC export, neither of which a malformed payload should cost.
2192
+ bytes = decodeSerializedTransaction(transaction, 'signTransaction');
2193
+ _state.label = 1;
2194
+ case 1:
2195
+ _state.trys.push([
2196
+ 1,
2197
+ 3,
2198
+ ,
2199
+ 4
2200
+ ]);
2201
+ return [
2202
+ 4,
2203
+ _this.withWallet('signTransaction', access, 'full', /*#__PURE__*/ function() {
2204
+ var _ref = _async_to_generator(function(param) {
2205
+ var wallet, unsignedTx, finalized;
2206
+ return _ts_generator(this, function(_state) {
2207
+ switch(_state.label){
2208
+ case 0:
2209
+ wallet = param.wallet;
2210
+ unsignedTx = ledger.Transaction.deserialize('signature', 'pre-proof', 'pre-binding', bytes);
2211
+ return [
2212
+ 4,
2213
+ _this.mpcSignIntents(unsignedTx, access, {
2214
+ fullTxBase64: transaction,
2215
+ networkId: resolvedNetworkId,
2216
+ baseContext: context
2217
+ })
2218
+ ];
2219
+ case 1:
2220
+ _state.sent();
2221
+ _this.logger.info('[Midnight] signTransaction: finalizing (ZK prove)…');
2222
+ return [
2223
+ 4,
2224
+ wallet.finalizeRecipe({
2225
+ type: 'UNPROVEN_TRANSACTION',
2226
+ transaction: unsignedTx
2227
+ })
2228
+ ];
2229
+ case 2:
2230
+ finalized = _state.sent();
2231
+ return [
2232
+ 2,
2233
+ Buffer.from(finalized.serialize()).toString('base64')
2234
+ ];
2235
+ }
2236
+ });
2237
+ });
2238
+ return function(_) {
2239
+ return _ref.apply(this, arguments);
2240
+ };
2241
+ }())
2242
+ ];
2243
+ case 2:
2244
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState;
2245
+ return [
2246
+ 2,
2247
+ {
2248
+ transaction: result,
2249
+ walletState: walletState
2250
+ }
2251
+ ];
2252
+ case 3:
2253
+ error = _state.sent();
2254
+ logError({
2255
+ message: ERROR_SIGN_TRANSACTION,
2256
+ error: error,
2257
+ context: {
2258
+ accountAddress: access.walletMetadata.accountAddress
2259
+ }
2260
+ });
2261
+ throw _instanceof(error, Error) ? error : new Error(ERROR_SIGN_TRANSACTION);
2262
+ case 4:
2263
+ return [
2264
+ 2
2265
+ ];
2266
+ }
2267
+ });
2268
+ })();
2269
+ }
2270
+ },
2271
+ {
2272
+ key: "getWalletProvider",
2273
+ value: /**
2274
+ * Returns a Midnight.js-compatible wallet provider for use with
2275
+ * midnight-js-contracts (deployment and contract calls). Signing goes through
2276
+ * MPC; no private key is handed to the caller.
2277
+ *
2278
+ * `balanceTx` and `submitTx` boot a wallet per call rather than closing over
2279
+ * one, so nothing is held open between the caller's contract operations. To
2280
+ * keep that from re-syncing from genesis every time, the provider carries the
2281
+ * checkpoint from one call into the next. That state lives in this closure
2282
+ * and dies with the provider — read it with `getWalletState()` to persist it
2283
+ * beyond that.
2284
+ */ function getWalletProvider(access) {
2285
+ var _this = this;
2286
+ return _async_to_generator(function() {
2287
+ var network, numericNetworkId, resume, durable, queue, withLatest, _ref, coinPublicKey, encryptionPublicKey;
2288
+ return _ts_generator(this, function(_state) {
2289
+ switch(_state.label){
2290
+ case 0:
2291
+ network = resolveMidnightNetworkTag(access.networkId);
2292
+ numericNetworkId = MIDNIGHT_NETWORK_IDS[network];
2293
+ // Two views of the same wallet. `resume` is what the next call boots from
2294
+ // and includes work in flight, so a second balanceTx cannot re-spend coins
2295
+ // the first one already reserved. `durable` is the clean subset handed to
2296
+ // the caller, who must not persist pending entries.
2297
+ resume = access.walletState;
2298
+ durable = access.walletState;
2299
+ // Each operation reads `resume`, awaits, then writes it back, so two
2300
+ // overlapping calls would both start from the same state and the later
2301
+ // write would drop the earlier one's reservations. Chaining them means a
2302
+ // caller that fires several without awaiting still gets them in order.
2303
+ queue = Promise.resolve();
2304
+ withLatest = function(label, use) {
2305
+ var run = queue.then(/*#__PURE__*/ _async_to_generator(function() {
2306
+ var _ref, result, walletState, sessionState;
2307
+ return _ts_generator(this, function(_state) {
2308
+ switch(_state.label){
2309
+ case 0:
2310
+ return [
2311
+ 4,
2312
+ _this.withWallet(label, _object_spread_props(_object_spread({}, access), {
2313
+ walletState: resume
2314
+ }), 'full', use)
2315
+ ];
2316
+ case 1:
2317
+ _ref = _state.sent(), result = _ref.result, walletState = _ref.walletState, sessionState = _ref.sessionState;
2318
+ if (sessionState) resume = sessionState;
2319
+ if (walletState) durable = walletState;
2320
+ return [
2321
+ 2,
2322
+ result
2323
+ ];
2324
+ }
2325
+ });
2326
+ }));
2327
+ // Swallow only for the chain's own sake — `run` still rejects for the
2328
+ // caller, but one failure must not poison every later operation.
2329
+ queue = run.catch(function() {
2330
+ return undefined;
2331
+ });
2332
+ return run;
2333
+ };
2334
+ return [
2335
+ 4,
2336
+ withLatest('walletProvider', /*#__PURE__*/ function() {
2337
+ var _ref = _async_to_generator(function(param) {
2338
+ var state;
2339
+ return _ts_generator(this, function(_state) {
2340
+ state = param.state;
2341
+ return [
2342
+ 2,
2343
+ {
2344
+ coinPublicKey: state.shielded.coinPublicKey.toHexString(),
2345
+ encryptionPublicKey: state.shielded.encryptionPublicKey.toHexString()
2346
+ }
2347
+ ];
2348
+ });
2349
+ });
2350
+ return function(_) {
2351
+ return _ref.apply(this, arguments);
2352
+ };
2353
+ }())
2354
+ ];
2355
+ case 1:
2356
+ _ref = _state.sent(), coinPublicKey = _ref.coinPublicKey, encryptionPublicKey = _ref.encryptionPublicKey;
2357
+ return [
2358
+ 2,
2359
+ {
2360
+ getCoinPublicKey: function() {
2361
+ return coinPublicKey;
2362
+ },
2363
+ getEncryptionPublicKey: function() {
2364
+ return encryptionPublicKey;
2365
+ },
2366
+ getWalletState: function() {
2367
+ return durable;
2368
+ },
2369
+ balanceTx: function(tx, ttl) {
2370
+ return withLatest('walletProvider.balanceTx', /*#__PURE__*/ function() {
2371
+ var _ref = _async_to_generator(function(param) {
2372
+ var wallet, shieldedSecretKeys, dustSecretKey, recipe;
2373
+ return _ts_generator(this, function(_state) {
2374
+ switch(_state.label){
2375
+ case 0:
2376
+ wallet = param.wallet, shieldedSecretKeys = param.shieldedSecretKeys, dustSecretKey = param.dustSecretKey;
2377
+ return [
2378
+ 4,
2379
+ wallet.balanceUnboundTransaction(tx, {
2380
+ shieldedSecretKeys: shieldedSecretKeys,
2381
+ dustSecretKey: dustSecretKey
2382
+ }, {
2383
+ ttl: ttl !== null && ttl !== void 0 ? ttl : new Date(Date.now() + 30 * 60 * 1000)
2384
+ })
2385
+ ];
2386
+ case 1:
2387
+ recipe = _state.sent();
2388
+ // The base transaction arrives already proven, so it deserializes and
2389
+ // signs under the 'proof' marker; the balancing transaction the SDK
2390
+ // adds has not been proven yet and uses 'pre-proof'.
2391
+ return [
2392
+ 4,
2393
+ _this.mpcSignIntents(recipe.baseTransaction, access, {
2394
+ fullTxBase64: Buffer.from(recipe.baseTransaction.serialize()).toString('base64'),
2395
+ networkId: numericNetworkId,
2396
+ proofMarker: 'proof'
2397
+ })
2398
+ ];
2399
+ case 2:
2400
+ _state.sent();
2401
+ if (!recipe.balancingTransaction) return [
2402
+ 3,
2403
+ 4
2404
+ ];
2405
+ return [
2406
+ 4,
2407
+ _this.mpcSignIntents(recipe.balancingTransaction, access, {
2408
+ fullTxBase64: Buffer.from(recipe.balancingTransaction.serialize()).toString('base64'),
2409
+ networkId: numericNetworkId,
2410
+ proofMarker: 'pre-proof'
2411
+ })
2412
+ ];
2413
+ case 3:
2414
+ _state.sent();
2415
+ _state.label = 4;
2416
+ case 4:
2417
+ _this.logger.info('[Midnight] walletProvider.balanceTx: finalizing (ZK prove)…');
2418
+ return [
2419
+ 2,
2420
+ wallet.finalizeRecipe(recipe)
2421
+ ];
2422
+ }
2423
+ });
2424
+ });
2425
+ return function(_) {
2426
+ return _ref.apply(this, arguments);
2427
+ };
2428
+ }());
2429
+ },
2430
+ submitTx: function(tx) {
2431
+ return withLatest('walletProvider.submitTx', /*#__PURE__*/ function() {
2432
+ var _ref = _async_to_generator(function(param) {
2433
+ var wallet;
2434
+ return _ts_generator(this, function(_state) {
2435
+ switch(_state.label){
2436
+ case 0:
2437
+ wallet = param.wallet;
2438
+ return [
2439
+ 4,
2440
+ _this.submitWithReconnect(wallet, tx)
2441
+ ];
2442
+ case 1:
2443
+ return [
2444
+ 2,
2445
+ toTxHashString.apply(void 0, [
2446
+ _state.sent()
2447
+ ])
2448
+ ];
2449
+ }
2450
+ });
2451
+ });
2452
+ return function(_) {
2453
+ return _ref.apply(this, arguments);
2454
+ };
2455
+ }());
2456
+ }
2457
+ }
2458
+ ];
2459
+ }
2460
+ });
2461
+ })();
2462
+ }
2463
+ },
2464
+ {
2465
+ key: "balanceContractTransaction",
2466
+ value: /**
2467
+ * Balances a serialized midnight-js UnboundTransaction and returns the
2468
+ * finalized transaction, base64-encoded. A transport-friendly wrapper over
2469
+ * {@link getWalletProvider}'s `balanceTx` for callers that pass transactions
2470
+ * across a process boundary rather than holding ledger objects.
2471
+ */ function balanceContractTransaction(access) {
2472
+ var _this = this;
2473
+ return _async_to_generator(function() {
2474
+ var transaction, ttl, ttlDate, bytes, unbound, provider, finalized, error;
2475
+ return _ts_generator(this, function(_state) {
2476
+ switch(_state.label){
2477
+ case 0:
2478
+ transaction = access.transaction, ttl = access.ttl;
2479
+ // `ttl ? …` would treat '' as absent and quietly apply the default lifetime.
2480
+ ttlDate = ttl === undefined ? undefined : parseIsoTimestamp(ttl, 'balanceContractTransaction');
2481
+ // Validated before the facade boots — see signTransaction. An unbound
2482
+ // contract transaction arrives proven, hence the 'proof' marker.
2483
+ bytes = decodeSerializedTransaction(transaction, 'balanceContractTransaction');
2484
+ _state.label = 1;
2485
+ case 1:
2486
+ _state.trys.push([
2487
+ 1,
2488
+ 4,
2489
+ ,
2490
+ 5
2491
+ ]);
2492
+ unbound = ledger.Transaction.deserialize('signature', 'proof', 'pre-binding', bytes);
2493
+ return [
2494
+ 4,
2495
+ _this.getWalletProvider(access)
2496
+ ];
2497
+ case 2:
2498
+ provider = _state.sent();
2499
+ return [
2500
+ 4,
2501
+ provider.balanceTx(unbound, ttlDate)
2502
+ ];
2503
+ case 3:
2504
+ finalized = _state.sent();
2505
+ // Booting the provider produces a clean checkpoint before any balancing
2506
+ // happens; dropping it would make the next call re-sync from genesis.
2507
+ return [
2508
+ 2,
2509
+ {
2510
+ transaction: Buffer.from(finalized.serialize()).toString('base64'),
2511
+ walletState: provider.getWalletState()
2512
+ }
2513
+ ];
2514
+ case 4:
2515
+ error = _state.sent();
2516
+ logError({
2517
+ message: ERROR_BALANCE_TRANSACTION,
2518
+ error: error,
2519
+ context: {
2520
+ accountAddress: access.walletMetadata.accountAddress
2521
+ }
2522
+ });
2523
+ throw _instanceof(error, Error) ? error : new Error(ERROR_BALANCE_TRANSACTION);
2524
+ case 5:
2525
+ return [
2526
+ 2
2527
+ ];
2528
+ }
2529
+ });
2530
+ })();
2531
+ }
2532
+ },
2533
+ {
2534
+ key: "mpcSignIntents",
2535
+ value: /**
2536
+ * Signs each intent segment via MPC and writes the signatures back onto the
2537
+ * transaction.
2538
+ *
2539
+ * Strictly serial. `tx.intents` is an immutable persistent map — `.set()`
2540
+ * returns a new map — so a Promise.all would have every iteration read the
2541
+ * same initial map and race to write back, silently dropping all but the last
2542
+ * segment's signature. Relay ceremonies serialize anyway, so little is lost.
2543
+ */ function mpcSignIntents(tx, access, options) {
2544
+ var _this = this;
2545
+ return _async_to_generator(function() {
2546
+ var _tx_intents, _tx_intents_keys_toArray, segments, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _loop, _iterator, _step, err;
2547
+ return _ts_generator(this, function(_state) {
2548
+ switch(_state.label){
2549
+ case 0:
2550
+ segments = (_tx_intents_keys_toArray = (_tx_intents = tx.intents) === null || _tx_intents === void 0 ? void 0 : _tx_intents.keys().toArray()) !== null && _tx_intents_keys_toArray !== void 0 ? _tx_intents_keys_toArray : [];
2551
+ if (segments.length === 0) return [
2552
+ 2
2553
+ ];
2554
+ _this.logger.info("[Midnight] MPC signing ".concat(segments.length, " intent segment(s)…"));
2555
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2556
+ _state.label = 1;
2557
+ case 1:
2558
+ _state.trys.push([
2559
+ 1,
2560
+ 6,
2561
+ 7,
2562
+ 8
2563
+ ]);
2564
+ _loop = function() {
2565
+ var _loop, segment, intent, _options_proofMarker, cloned, sigData, hash, signature, sigBytes, ledgerSig, _i, _iter;
2566
+ return _ts_generator(this, function(_state) {
2567
+ switch(_state.label){
2568
+ case 0:
2569
+ _loop = function(_i, _iter) {
2570
+ var offerKey = _iter[_i];
2571
+ var offer = cloned[offerKey];
2572
+ if (!offer) return "continue";
2573
+ cloned[offerKey] = offer.addSignatures(offer.inputs.map(function(_, index) {
2574
+ var _offer_signatures_at;
2575
+ return (_offer_signatures_at = offer.signatures.at(index)) !== null && _offer_signatures_at !== void 0 ? _offer_signatures_at : ledgerSig;
2576
+ }));
2577
+ };
2578
+ segment = _step.value;
2579
+ intent = tx.intents.get(segment);
2580
+ if (!intent) return [
2581
+ 2,
2582
+ "continue"
2583
+ ];
2584
+ cloned = ledger.Intent.deserialize('signature', (_options_proofMarker = options.proofMarker) !== null && _options_proofMarker !== void 0 ? _options_proofMarker : 'pre-proof', 'pre-binding', intent.serialize());
2585
+ sigData = cloned.signatureData(segment);
2586
+ hash = createHash('sha256').update(Buffer.from(sigData)).digest('hex');
2587
+ return [
2588
+ 4,
2589
+ _this.sign({
2590
+ message: hash,
2591
+ accountAddress: access.walletMetadata.accountAddress,
2592
+ chainName: _this.chainName,
2593
+ password: access.password,
2594
+ externalServerKeyShares: access.externalServerKeyShares,
2595
+ walletMetadata: access.walletMetadata,
2596
+ walletOperation: WalletOperation.SIGN_TRANSACTION,
2597
+ // Policy validation runs per signature, so every segment carries the
2598
+ // whole transaction — same shape as BTC's per-input PSBT context.
2599
+ context: _object_spread_props(_object_spread({}, options.baseContext), {
2600
+ midnightTransaction: {
2601
+ serializedTransaction: options.fullTxBase64,
2602
+ networkId: options.networkId
2603
+ }
2604
+ })
2605
+ })
2606
+ ];
2607
+ case 1:
2608
+ signature = _state.sent();
2609
+ sigBytes = _instanceof(signature, Uint8Array) ? signature : Buffer.from(String(signature), 'base64');
2610
+ ledgerSig = Buffer.from(sigBytes).toString('hex');
2611
+ // Preserve any signature already present at an input index; only fill gaps.
2612
+ for(_i = 0, _iter = [
2613
+ 'guaranteedUnshieldedOffer',
2614
+ 'fallibleUnshieldedOffer'
2615
+ ]; _i < _iter.length; _i++)_loop(_i, _iter);
2616
+ tx.intents = tx.intents.set(segment, cloned);
2617
+ return [
2618
+ 2
2619
+ ];
2620
+ }
2621
+ });
2622
+ };
2623
+ _iterator = segments[Symbol.iterator]();
2624
+ _state.label = 2;
2625
+ case 2:
2626
+ if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [
2627
+ 3,
2628
+ 5
2629
+ ];
2630
+ return [
2631
+ 5,
2632
+ _ts_values(_loop())
2633
+ ];
2634
+ case 3:
2635
+ _state.sent();
2636
+ _state.label = 4;
2637
+ case 4:
2638
+ _iteratorNormalCompletion = true;
2639
+ return [
2640
+ 3,
2641
+ 2
2642
+ ];
2643
+ case 5:
2644
+ return [
2645
+ 3,
2646
+ 8
2647
+ ];
2648
+ case 6:
2649
+ err = _state.sent();
2650
+ _didIteratorError = true;
2651
+ _iteratorError = err;
2652
+ return [
2653
+ 3,
2654
+ 8
2655
+ ];
2656
+ case 7:
2657
+ try {
2658
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2659
+ _iterator.return();
2660
+ }
2661
+ } finally{
2662
+ if (_didIteratorError) {
2663
+ throw _iteratorError;
2664
+ }
2665
+ }
2666
+ return [
2667
+ 7
2668
+ ];
2669
+ case 8:
2670
+ return [
2671
+ 2
2672
+ ];
2673
+ }
2674
+ });
2675
+ })();
2676
+ }
2677
+ },
1025
2678
  {
1026
2679
  key: "getMidnightWallets",
1027
2680
  value: /**
@@ -1055,4 +2708,4 @@ var DynamicMidnightWalletClient = /*#__PURE__*/ function(DynamicWalletClient) {
1055
2708
  return DynamicMidnightWalletClient;
1056
2709
  }(DynamicWalletClient);
1057
2710
 
1058
- export { DynamicMidnightWalletClient, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_KEYGEN_FAILED, ERROR_SIGN_MESSAGE, MIDNIGHT_NETWORK_IDS, decodeMidnightAddress, deriveMidnightAddress, getNetworkFromAddress, isValidMidnightAddress, resolveMidnightNetworkTag, toXOnlyPublicKeyHex };
2711
+ export { DynamicMidnightWalletClient, ERROR_ACCOUNT_ADDRESS_REQUIRED, ERROR_BALANCE_TRANSACTION, ERROR_CREATE_WALLET_ACCOUNT, ERROR_EXPORT_PRIVATE_KEY, ERROR_IMPORT_PRIVATE_KEY, ERROR_KEYGEN_FAILED, ERROR_SIGN_MESSAGE, MIDNIGHT_NETWORK_IDS, decodeMidnightAddress, deriveMidnightAddress, getNetworkFromAddress, isValidMidnightAddress, resolveMidnightNetworkTag, toXOnlyPublicKeyHex };