@dynamic-labs/midnight 4.91.0 → 4.91.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,47 @@
1
1
 
2
+ ### [4.91.2](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.1...v4.91.2) (2026-06-26)
3
+
4
+
5
+ ### ⚠ BREAKING CHANGES
6
+
7
+ * **waas:** require expectedAddresses on migrateFromFireblocks (#11730)
8
+
9
+ ### Features
10
+
11
+ * **midnight:** support shielded/unshielded transfers and dust states in the embedded Send flow ([#11731](https://github.com/dynamic-labs/dynamic-auth/issues/11731)) ([663d171](https://github.com/dynamic-labs/dynamic-auth/commit/663d171a9628c634586831ee35c234e9331e3f80))
12
+ * **react-native-extension:** instrument all embedded webview failure paths ([#11737](https://github.com/dynamic-labs/dynamic-auth/issues/11737)) ([172998b](https://github.com/dynamic-labs/dynamic-auth/commit/172998b56e9bee04fbb8da71e83ce8885bc378ff))
13
+ * **waas:** migrateFromFireblocks connector method + demo panel (browser-wallet-client 1.0.46) ([#11702](https://github.com/dynamic-labs/dynamic-auth/issues/11702)) ([6bfc844](https://github.com/dynamic-labs/dynamic-auth/commit/6bfc8443fbaedabe1e7bee0ed78742cbbe5d0084))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * add coverage for scenario with passkey re-login and smart account ([#11726](https://github.com/dynamic-labs/dynamic-auth/issues/11726)) ([5364a72](https://github.com/dynamic-labs/dynamic-auth/commit/5364a72a750d835b9b2da602a91e8b5d7d305db7))
19
+ * remediate high-severity dependency vulnerabilities ([#11721](https://github.com/dynamic-labs/dynamic-auth/issues/11721)) ([cdb9306](https://github.com/dynamic-labs/dynamic-auth/commit/cdb93061365a20b8f73a1f8bea8352332e282c1e))
20
+ * replace node-fetch v2 with native fetch to fix Node 22 stream failure ([#11732](https://github.com/dynamic-labs/dynamic-auth/issues/11732)) ([b98f801](https://github.com/dynamic-labs/dynamic-auth/commit/b98f801e307b52dbd7e4b241a9a4a2d34d89118e))
21
+ * **zerodev-extension:** derive kernel 7702 vs 4337 per-wallet, not from global dashboard flag ([#11735](https://github.com/dynamic-labs/dynamic-auth/issues/11735)) ([72cf6a7](https://github.com/dynamic-labs/dynamic-auth/commit/72cf6a70a4857a4ea469faa555c485dbad8d0217))
22
+
23
+
24
+ * **waas:** require expectedAddresses on migrateFromFireblocks ([#11730](https://github.com/dynamic-labs/dynamic-auth/issues/11730)) ([c94824e](https://github.com/dynamic-labs/dynamic-auth/commit/c94824ebe11e0433a6ffbd2a5a9dd4283aeed31e))
25
+
26
+ ### [4.91.1](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.0...v4.91.1) (2026-06-24)
27
+
28
+
29
+ ### Features
30
+
31
+ * **aleo:** update sendBalance with individual and marketplace logic ([#11709](https://github.com/dynamic-labs/dynamic-auth/issues/11709)) ([f8dfc48](https://github.com/dynamic-labs/dynamic-auth/commit/f8dfc488ae607fbbbcc3432a3ef69f746f0b7df0))
32
+ * **sdk-react-core:** surface shielded address in Midnight deposit view ([#11698](https://github.com/dynamic-labs/dynamic-auth/issues/11698)) ([79c5402](https://github.com/dynamic-labs/dynamic-auth/commit/79c54029b1d8f722cf9f1d8b4c11977898442ac0))
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * display actual error message for WaaS export failures instead of generic fallback ([#11498](https://github.com/dynamic-labs/dynamic-auth/issues/11498)) ([373c931](https://github.com/dynamic-labs/dynamic-auth/commit/373c931eb96c88e484c557ee8f2934660d028f97))
38
+ * **wallet-book:** guard replaceKey and remove stale override ([#11682](https://github.com/dynamic-labs/dynamic-auth/issues/11682)) ([13947d9](https://github.com/dynamic-labs/dynamic-auth/commit/13947d972ed1f8227712bfc01af9cc2be616a78a))
39
+
40
+
41
+ ### Performance Improvements
42
+
43
+ * **e2e:** prebuild Expo web bundle for RN e2e tests ([#11674](https://github.com/dynamic-labs/dynamic-auth/issues/11674)) ([16e002d](https://github.com/dynamic-labs/dynamic-auth/commit/16e002d59b2035e3f2c116879a78418a959da8bc))
44
+
2
45
  ## [4.91.0](https://github.com/dynamic-labs/dynamic-auth/compare/v4.90.0...v4.91.0) (2026-06-23)
3
46
 
4
47
 
package/package.cjs CHANGED
@@ -3,6 +3,6 @@
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
- var version = "4.91.0";
6
+ var version = "4.91.2";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.91.0";
2
+ var version = "4.91.2";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/midnight",
3
- "version": "4.91.0",
3
+ "version": "4.91.2",
4
4
  "description": "A React SDK for implementing wallet web3 authentication and authorization to your website.",
5
5
  "author": "Dynamic Labs, Inc.",
6
6
  "license": "MIT",
@@ -18,15 +18,15 @@
18
18
  },
19
19
  "homepage": "https://www.dynamic.xyz/",
20
20
  "dependencies": {
21
- "@dynamic-labs/assert-package-version": "4.91.0",
21
+ "@dynamic-labs/assert-package-version": "4.91.2",
22
22
  "@dynamic-labs/sdk-api-core": "0.0.1046",
23
23
  "@midnight-ntwrk/dapp-connector-api": "4.0.1",
24
- "@dynamic-labs/logger": "4.91.0",
25
- "@dynamic-labs/types": "4.91.0",
26
- "@dynamic-labs/utils": "4.91.0",
27
- "@dynamic-labs/waas": "4.91.0",
28
- "@dynamic-labs/wallet-book": "4.91.0",
29
- "@dynamic-labs/wallet-connector-core": "4.91.0"
24
+ "@dynamic-labs/logger": "4.91.2",
25
+ "@dynamic-labs/types": "4.91.2",
26
+ "@dynamic-labs/utils": "4.91.2",
27
+ "@dynamic-labs/waas": "4.91.2",
28
+ "@dynamic-labs/wallet-book": "4.91.2",
29
+ "@dynamic-labs/wallet-connector-core": "4.91.2"
30
30
  },
31
31
  "peerDependencies": {}
32
32
  }
@@ -74,6 +74,35 @@ class MidnightWalletConnector extends walletConnectorCore.WalletConnectorBase {
74
74
  throw new Error('signTransaction is not supported by this connector');
75
75
  });
76
76
  }
77
+ createTransferTransaction(
78
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
79
+ params) {
80
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
81
+ throw new Error('createTransferTransaction is not supported by this connector');
82
+ });
83
+ }
84
+ registerDust() {
85
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
86
+ throw new Error('registerDust is not supported by this connector');
87
+ });
88
+ }
89
+ revertTransaction(
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
91
+ serializedTransaction) {
92
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
93
+ throw new Error('revertTransaction is not supported by this connector');
94
+ });
95
+ }
96
+ revertAllPending() {
97
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
98
+ throw new Error('revertAllPending is not supported by this connector');
99
+ });
100
+ }
101
+ resetWalletCache() {
102
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
103
+ throw new Error('resetWalletCache is not supported by this connector');
104
+ });
105
+ }
77
106
  getShieldedBalances() {
78
107
  return _tslib.__awaiter(this, void 0, void 0, function* () {
79
108
  const api = yield this.getConnectedAPI();
@@ -31,6 +31,42 @@ export declare abstract class MidnightWalletConnector extends WalletConnectorBas
31
31
  }>;
32
32
  }): Promise<string>;
33
33
  signTransaction(serializedTransaction: string): Promise<string>;
34
+ createTransferTransaction(params: {
35
+ transfers: Array<{
36
+ type: 'unshielded' | 'shielded';
37
+ recipientAddress: string;
38
+ amount: string;
39
+ tokenType?: string;
40
+ }>;
41
+ ttlMinutes?: number;
42
+ }): Promise<{
43
+ serializedTransaction: string;
44
+ }>;
45
+ registerDust(): Promise<{
46
+ dustBalance: string;
47
+ txId?: string;
48
+ status: 'registered' | 'already_registered' | 'already_has_dust' | 'no_utxos';
49
+ message: string;
50
+ registeredCount: number;
51
+ }>;
52
+ revertTransaction(serializedTransaction: string): Promise<{
53
+ reverted: boolean;
54
+ count: number;
55
+ cacheCleared: boolean;
56
+ message: string;
57
+ }>;
58
+ revertAllPending(): Promise<{
59
+ reverted: boolean;
60
+ count: number;
61
+ cacheCleared: boolean;
62
+ message: string;
63
+ }>;
64
+ resetWalletCache(): Promise<{
65
+ reverted: boolean;
66
+ count: number;
67
+ cacheCleared: boolean;
68
+ message: string;
69
+ }>;
34
70
  getShieldedBalances(): Promise<Record<TokenType, bigint>>;
35
71
  getUnshieldedBalances(): Promise<Record<TokenType, bigint>>;
36
72
  getDustBalance(): Promise<{
@@ -70,6 +70,35 @@ class MidnightWalletConnector extends WalletConnectorBase {
70
70
  throw new Error('signTransaction is not supported by this connector');
71
71
  });
72
72
  }
73
+ createTransferTransaction(
74
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
75
+ params) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ throw new Error('createTransferTransaction is not supported by this connector');
78
+ });
79
+ }
80
+ registerDust() {
81
+ return __awaiter(this, void 0, void 0, function* () {
82
+ throw new Error('registerDust is not supported by this connector');
83
+ });
84
+ }
85
+ revertTransaction(
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
87
+ serializedTransaction) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ throw new Error('revertTransaction is not supported by this connector');
90
+ });
91
+ }
92
+ revertAllPending() {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ throw new Error('revertAllPending is not supported by this connector');
95
+ });
96
+ }
97
+ resetWalletCache() {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ throw new Error('resetWalletCache is not supported by this connector');
100
+ });
101
+ }
73
102
  getShieldedBalances() {
74
103
  return __awaiter(this, void 0, void 0, function* () {
75
104
  const api = yield this.getConnectedAPI();
@@ -8,6 +8,10 @@ var constants = require('../../constants.cjs');
8
8
  var formatMidnightAtomicBalance = require('../formatMidnightAtomicBalance/formatMidnightAtomicBalance.cjs');
9
9
  var parseMidnightAtomicAmount = require('../parseMidnightAtomicAmount/parseMidnightAtomicAmount.cjs');
10
10
 
11
+ // Shielded tokens aren't NIGHT and have no decimals metadata, so they're shown
12
+ // at their raw amount (decimals 0); native NIGHT keeps 6-decimal formatting.
13
+ const SHIELDED_TOKEN_DECIMALS = 0;
14
+ const shortTokenLabel = (address) => `TOKEN: ${address.slice(0, 8)}...${address.slice(-8)}`;
11
15
  class MidnightUiTransaction {
12
16
  constructor(opts) {
13
17
  this.to = undefined;
@@ -17,68 +21,104 @@ class MidnightUiTransaction {
17
21
  this.chain = 'MIDNIGHT';
18
22
  this.fee = { gas: undefined };
19
23
  this.feeTokenSymbol = 'DUST';
24
+ // Pool defaults to unshielded; it is derived from the selected token (or the
25
+ // transaction-mode toggle), never inferred from a hard-coded token hash.
20
26
  this.pool = 'unshielded';
27
+ this.selectedTransactionMode = 'unshielded';
28
+ this.selectedTokenAddress = constants.MIDNIGHT_NATIVE_TOKEN_TYPE;
21
29
  this.from = opts.from;
30
+ this.balances = opts.balances;
31
+ this.shieldedAddresses = new Set(Object.keys(opts.balances.shielded));
22
32
  this.onSubmitFn = opts.onSubmit;
23
- this.getUnshieldedBalanceFn = opts.getUnshieldedBalance;
24
- this.getShieldedBalanceFn = opts.getShieldedBalance;
25
33
  this.fetchDustFeeFn = opts.fetchDustFee;
26
34
  }
27
35
  get activePool() {
28
36
  return this.pool;
29
37
  }
38
+ get transactionModes() {
39
+ return [
40
+ { id: 'unshielded', label: 'Unshielded' },
41
+ { id: 'shielded', label: 'Shielded' },
42
+ ];
43
+ }
44
+ get selectedTokenDecimals() {
45
+ return this.selectedTokenAddress === constants.MIDNIGHT_NATIVE_TOKEN_TYPE
46
+ ? constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS
47
+ : SHIELDED_TOKEN_DECIMALS;
48
+ }
49
+ // Pool a token belongs to — used by the send form to filter the picker.
50
+ getTokenPool(contractAddress) {
51
+ return this.shieldedAddresses.has(contractAddress)
52
+ ? 'shielded'
53
+ : 'unshielded';
54
+ }
55
+ setTransactionMode(modeId) {
56
+ this.selectedTransactionMode = modeId;
57
+ this.pool = modeId === 'shielded' ? 'shielded' : 'unshielded';
58
+ this.to = undefined;
59
+ this.value = undefined;
60
+ }
30
61
  setSelectedToken(contractAddress) {
31
- this.pool =
32
- contractAddress === constants.MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE
33
- ? 'shielded'
34
- : 'unshielded';
62
+ this.selectedTokenAddress = contractAddress;
63
+ this.pool = this.getTokenPool(contractAddress);
64
+ this.selectedTransactionMode = this.pool;
35
65
  this.to = undefined;
36
66
  this.value = undefined;
37
67
  }
38
68
  getSendableTokenBalances() {
39
69
  return _tslib.__awaiter(this, void 0, void 0, function* () {
40
- const [unshieldedRaw, shieldedRaw] = yield Promise.all([
41
- this.getUnshieldedBalanceFn(),
42
- this.getShieldedBalanceFn(),
43
- ]);
44
- const toDisplay = (raw) => Number(formatMidnightAtomicBalance.formatMidnightAtomicBalance(raw, constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS));
45
- return [
46
- {
47
- address: constants.MIDNIGHT_NATIVE_TOKEN_TYPE,
48
- balance: toDisplay(unshieldedRaw),
49
- decimals: constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
50
- isNative: true,
51
- logoURI: '',
52
- name: 'NIGHT (Unshielded)',
53
- rawBalance: Number(unshieldedRaw),
54
- rawBalanceString: unshieldedRaw.toString(),
55
- symbol: 'NIGHT',
56
- },
57
- {
58
- address: constants.MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE,
59
- balance: toDisplay(shieldedRaw),
60
- decimals: constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
61
- isNative: true,
62
- logoURI: '',
63
- name: 'NIGHT (Shielded)',
64
- rawBalance: Number(shieldedRaw),
65
- rawBalanceString: shieldedRaw.toString(),
66
- symbol: 'NIGHT',
67
- },
68
- ];
70
+ var _a;
71
+ const toDisplay = (raw, decimals) => Number(formatMidnightAtomicBalance.formatMidnightAtomicBalance(raw, decimals));
72
+ // Native unshielded NIGHT — always shown, even at a zero balance.
73
+ const unshieldedNightRaw = (_a = this.balances.unshielded[constants.MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
74
+ const nightEntry = {
75
+ address: constants.MIDNIGHT_NATIVE_TOKEN_TYPE,
76
+ balance: toDisplay(unshieldedNightRaw, constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS),
77
+ decimals: constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
78
+ isNative: true,
79
+ logoURI: '',
80
+ name: 'NIGHT',
81
+ rawBalance: Number(unshieldedNightRaw),
82
+ rawBalanceString: unshieldedNightRaw.toString(),
83
+ symbol: 'NIGHT',
84
+ };
85
+ // Non-NIGHT tokens (other unshielded + every shielded token), shown at raw
86
+ // amount. `isNative: true` is deliberate — like Aleo, every Midnight token
87
+ // uses the `transaction.parse`/`getBalance` path; the form's non-native
88
+ // branch (`parseNonNativeToken`) is EVM-only and doesn't apply here.
89
+ const tokenEntry = (address, raw) => ({
90
+ address,
91
+ balance: toDisplay(raw, SHIELDED_TOKEN_DECIMALS),
92
+ decimals: SHIELDED_TOKEN_DECIMALS,
93
+ isNative: true,
94
+ logoURI: '',
95
+ name: shortTokenLabel(address),
96
+ rawBalance: Number(raw),
97
+ rawBalanceString: raw.toString(),
98
+ symbol: '',
99
+ });
100
+ const otherUnshielded = Object.entries(this.balances.unshielded)
101
+ .filter(([address, raw]) => address !== constants.MIDNIGHT_NATIVE_TOKEN_TYPE && raw > BigInt(0))
102
+ .map(([address, raw]) => tokenEntry(address, raw));
103
+ const shielded = Object.entries(this.balances.shielded)
104
+ .filter(([, raw]) => raw > BigInt(0))
105
+ .map(([address, raw]) => tokenEntry(address, raw));
106
+ return [nightEntry, ...otherUnshielded, ...shielded];
69
107
  });
70
108
  }
71
109
  parse(input) {
72
- return parseMidnightAtomicAmount.parseMidnightAtomicAmount(input, constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS);
110
+ return parseMidnightAtomicAmount.parseMidnightAtomicAmount(input, this.selectedTokenDecimals);
73
111
  }
74
112
  format(value) {
75
- return formatMidnightAtomicBalance.formatMidnightAtomicBalance(value, constants.MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS);
113
+ return formatMidnightAtomicBalance.formatMidnightAtomicBalance(value, this.selectedTokenDecimals);
76
114
  }
77
115
  getBalance() {
78
116
  return _tslib.__awaiter(this, void 0, void 0, function* () {
79
- return this.pool === 'shielded'
80
- ? this.getShieldedBalanceFn()
81
- : this.getUnshieldedBalanceFn();
117
+ var _a;
118
+ const map = this.pool === 'shielded'
119
+ ? this.balances.shielded
120
+ : this.balances.unshielded;
121
+ return (_a = map[this.selectedTokenAddress]) !== null && _a !== void 0 ? _a : BigInt(0);
82
122
  });
83
123
  }
84
124
  fetchFee() {
@@ -1,11 +1,14 @@
1
1
  import type { TokenBalance } from '@dynamic-labs/sdk-api-core';
2
- import type { IUITransaction } from '@dynamic-labs/types';
2
+ import type { IUITransaction, TransactionModeOption } from '@dynamic-labs/types';
3
3
  type MidnightPool = 'unshielded' | 'shielded';
4
+ export type MidnightBalanceMaps = {
5
+ shielded: Record<string, bigint>;
6
+ unshielded: Record<string, bigint>;
7
+ };
4
8
  type MidnightUiTransactionOpts = {
5
9
  from: string;
6
10
  onSubmit: (tx: MidnightUiTransaction) => Promise<string>;
7
- getUnshieldedBalance: () => Promise<bigint>;
8
- getShieldedBalance: () => Promise<bigint>;
11
+ balances: MidnightBalanceMaps;
9
12
  fetchDustFee: () => Promise<bigint>;
10
13
  };
11
14
  export declare class MidnightUiTransaction implements IUITransaction {
@@ -20,12 +23,18 @@ export declare class MidnightUiTransaction implements IUITransaction {
20
23
  };
21
24
  feeTokenSymbol: string;
22
25
  private pool;
26
+ selectedTransactionMode: string;
27
+ selectedTokenAddress: string;
28
+ private readonly balances;
29
+ private readonly shieldedAddresses;
23
30
  private readonly onSubmitFn;
24
- private readonly getUnshieldedBalanceFn;
25
- private readonly getShieldedBalanceFn;
26
31
  private readonly fetchDustFeeFn;
27
32
  constructor(opts: MidnightUiTransactionOpts);
28
33
  get activePool(): MidnightPool;
34
+ get transactionModes(): TransactionModeOption[];
35
+ private get selectedTokenDecimals();
36
+ getTokenPool(contractAddress: string): MidnightPool;
37
+ setTransactionMode(modeId: string): void;
29
38
  setSelectedToken(contractAddress: string): void;
30
39
  getSendableTokenBalances(): Promise<TokenBalance[]>;
31
40
  parse(input: string): bigint;
@@ -1,9 +1,13 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../../_virtual/_tslib.js';
3
- import { MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE, MIDNIGHT_NATIVE_TOKEN_TYPE, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS } from '../../constants.js';
3
+ import { MIDNIGHT_NATIVE_TOKEN_TYPE, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS } from '../../constants.js';
4
4
  import { formatMidnightAtomicBalance } from '../formatMidnightAtomicBalance/formatMidnightAtomicBalance.js';
5
5
  import { parseMidnightAtomicAmount } from '../parseMidnightAtomicAmount/parseMidnightAtomicAmount.js';
6
6
 
7
+ // Shielded tokens aren't NIGHT and have no decimals metadata, so they're shown
8
+ // at their raw amount (decimals 0); native NIGHT keeps 6-decimal formatting.
9
+ const SHIELDED_TOKEN_DECIMALS = 0;
10
+ const shortTokenLabel = (address) => `TOKEN: ${address.slice(0, 8)}...${address.slice(-8)}`;
7
11
  class MidnightUiTransaction {
8
12
  constructor(opts) {
9
13
  this.to = undefined;
@@ -13,68 +17,104 @@ class MidnightUiTransaction {
13
17
  this.chain = 'MIDNIGHT';
14
18
  this.fee = { gas: undefined };
15
19
  this.feeTokenSymbol = 'DUST';
20
+ // Pool defaults to unshielded; it is derived from the selected token (or the
21
+ // transaction-mode toggle), never inferred from a hard-coded token hash.
16
22
  this.pool = 'unshielded';
23
+ this.selectedTransactionMode = 'unshielded';
24
+ this.selectedTokenAddress = MIDNIGHT_NATIVE_TOKEN_TYPE;
17
25
  this.from = opts.from;
26
+ this.balances = opts.balances;
27
+ this.shieldedAddresses = new Set(Object.keys(opts.balances.shielded));
18
28
  this.onSubmitFn = opts.onSubmit;
19
- this.getUnshieldedBalanceFn = opts.getUnshieldedBalance;
20
- this.getShieldedBalanceFn = opts.getShieldedBalance;
21
29
  this.fetchDustFeeFn = opts.fetchDustFee;
22
30
  }
23
31
  get activePool() {
24
32
  return this.pool;
25
33
  }
34
+ get transactionModes() {
35
+ return [
36
+ { id: 'unshielded', label: 'Unshielded' },
37
+ { id: 'shielded', label: 'Shielded' },
38
+ ];
39
+ }
40
+ get selectedTokenDecimals() {
41
+ return this.selectedTokenAddress === MIDNIGHT_NATIVE_TOKEN_TYPE
42
+ ? MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS
43
+ : SHIELDED_TOKEN_DECIMALS;
44
+ }
45
+ // Pool a token belongs to — used by the send form to filter the picker.
46
+ getTokenPool(contractAddress) {
47
+ return this.shieldedAddresses.has(contractAddress)
48
+ ? 'shielded'
49
+ : 'unshielded';
50
+ }
51
+ setTransactionMode(modeId) {
52
+ this.selectedTransactionMode = modeId;
53
+ this.pool = modeId === 'shielded' ? 'shielded' : 'unshielded';
54
+ this.to = undefined;
55
+ this.value = undefined;
56
+ }
26
57
  setSelectedToken(contractAddress) {
27
- this.pool =
28
- contractAddress === MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE
29
- ? 'shielded'
30
- : 'unshielded';
58
+ this.selectedTokenAddress = contractAddress;
59
+ this.pool = this.getTokenPool(contractAddress);
60
+ this.selectedTransactionMode = this.pool;
31
61
  this.to = undefined;
32
62
  this.value = undefined;
33
63
  }
34
64
  getSendableTokenBalances() {
35
65
  return __awaiter(this, void 0, void 0, function* () {
36
- const [unshieldedRaw, shieldedRaw] = yield Promise.all([
37
- this.getUnshieldedBalanceFn(),
38
- this.getShieldedBalanceFn(),
39
- ]);
40
- const toDisplay = (raw) => Number(formatMidnightAtomicBalance(raw, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS));
41
- return [
42
- {
43
- address: MIDNIGHT_NATIVE_TOKEN_TYPE,
44
- balance: toDisplay(unshieldedRaw),
45
- decimals: MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
46
- isNative: true,
47
- logoURI: '',
48
- name: 'NIGHT (Unshielded)',
49
- rawBalance: Number(unshieldedRaw),
50
- rawBalanceString: unshieldedRaw.toString(),
51
- symbol: 'NIGHT',
52
- },
53
- {
54
- address: MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE,
55
- balance: toDisplay(shieldedRaw),
56
- decimals: MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
57
- isNative: true,
58
- logoURI: '',
59
- name: 'NIGHT (Shielded)',
60
- rawBalance: Number(shieldedRaw),
61
- rawBalanceString: shieldedRaw.toString(),
62
- symbol: 'NIGHT',
63
- },
64
- ];
66
+ var _a;
67
+ const toDisplay = (raw, decimals) => Number(formatMidnightAtomicBalance(raw, decimals));
68
+ // Native unshielded NIGHT — always shown, even at a zero balance.
69
+ const unshieldedNightRaw = (_a = this.balances.unshielded[MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
70
+ const nightEntry = {
71
+ address: MIDNIGHT_NATIVE_TOKEN_TYPE,
72
+ balance: toDisplay(unshieldedNightRaw, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS),
73
+ decimals: MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS,
74
+ isNative: true,
75
+ logoURI: '',
76
+ name: 'NIGHT',
77
+ rawBalance: Number(unshieldedNightRaw),
78
+ rawBalanceString: unshieldedNightRaw.toString(),
79
+ symbol: 'NIGHT',
80
+ };
81
+ // Non-NIGHT tokens (other unshielded + every shielded token), shown at raw
82
+ // amount. `isNative: true` is deliberate — like Aleo, every Midnight token
83
+ // uses the `transaction.parse`/`getBalance` path; the form's non-native
84
+ // branch (`parseNonNativeToken`) is EVM-only and doesn't apply here.
85
+ const tokenEntry = (address, raw) => ({
86
+ address,
87
+ balance: toDisplay(raw, SHIELDED_TOKEN_DECIMALS),
88
+ decimals: SHIELDED_TOKEN_DECIMALS,
89
+ isNative: true,
90
+ logoURI: '',
91
+ name: shortTokenLabel(address),
92
+ rawBalance: Number(raw),
93
+ rawBalanceString: raw.toString(),
94
+ symbol: '',
95
+ });
96
+ const otherUnshielded = Object.entries(this.balances.unshielded)
97
+ .filter(([address, raw]) => address !== MIDNIGHT_NATIVE_TOKEN_TYPE && raw > BigInt(0))
98
+ .map(([address, raw]) => tokenEntry(address, raw));
99
+ const shielded = Object.entries(this.balances.shielded)
100
+ .filter(([, raw]) => raw > BigInt(0))
101
+ .map(([address, raw]) => tokenEntry(address, raw));
102
+ return [nightEntry, ...otherUnshielded, ...shielded];
65
103
  });
66
104
  }
67
105
  parse(input) {
68
- return parseMidnightAtomicAmount(input, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS);
106
+ return parseMidnightAtomicAmount(input, this.selectedTokenDecimals);
69
107
  }
70
108
  format(value) {
71
- return formatMidnightAtomicBalance(value, MIDNIGHT_NATIVE_UNSHIELDED_DECIMALS);
109
+ return formatMidnightAtomicBalance(value, this.selectedTokenDecimals);
72
110
  }
73
111
  getBalance() {
74
112
  return __awaiter(this, void 0, void 0, function* () {
75
- return this.pool === 'shielded'
76
- ? this.getShieldedBalanceFn()
77
- : this.getUnshieldedBalanceFn();
113
+ var _a;
114
+ const map = this.pool === 'shielded'
115
+ ? this.balances.shielded
116
+ : this.balances.unshielded;
117
+ return (_a = map[this.selectedTokenAddress]) !== null && _a !== void 0 ? _a : BigInt(0);
78
118
  });
79
119
  }
80
120
  fetchFee() {
@@ -18,8 +18,6 @@ var buildMidnightWaasConnectedApi = require('./buildMidnightWaasConnectedApi.cjs
18
18
  /**
19
19
  * WaaS (Wallet-as-a-Service) connector for Midnight blockchain.
20
20
  * Extends the WaaS-specific Midnight base with MPC wallet functionality.
21
- *
22
- * Phase 1: Supports unshielded assets (NIGHT, MNTs) only.
23
21
  */
24
22
  class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWalletConnector.WaasMidnightWalletConnector) {
25
23
  constructor(props) {
@@ -89,6 +87,8 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
89
87
  */
90
88
  afterWalletSelectHook(walletAddress) {
91
89
  this.setActiveAccountAddress(walletAddress);
90
+ // Different wallet → its balances are unrelated; drop any cached snapshot.
91
+ this.clearSendBalanceCache();
92
92
  }
93
93
  /**
94
94
  * Requires and returns the signed session ID
@@ -167,7 +167,9 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
167
167
  */
168
168
  disconnect() {
169
169
  return _tslib.__awaiter(this, void 0, void 0, function* () {
170
- // WaaS doesn't require explicit disconnect
170
+ // WaaS doesn't require explicit disconnect, but drop the balance snapshot
171
+ // so a shielded balance isn't retained after the wallet is disconnected.
172
+ this.clearSendBalanceCache();
171
173
  });
172
174
  }
173
175
  /**
@@ -270,47 +272,54 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
270
272
  return signature;
271
273
  });
272
274
  }
273
- /**
274
- * Creates a UI transaction for the send balance flow
275
- * @param from - The sender's address
276
- * @returns A new IUITransaction instance
277
- */
275
+ clearSendBalanceCache() {
276
+ this.sendBalanceCache = undefined;
277
+ }
278
278
  createUiTransaction(from) {
279
279
  return _tslib.__awaiter(this, void 0, void 0, function* () {
280
280
  yield this.validateActiveWallet(from);
281
+ // Balances from the connector (MPC state) — never the redcoast
282
+ // public-balance endpoint, which rejects Midnight addresses. Reused from the
283
+ // short-lived cache across recreations (see field doc).
284
+ const now = Date.now();
285
+ if (!this.sendBalanceCache ||
286
+ now - this.sendBalanceCache.at >
287
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS) {
288
+ const { shielded, unshielded } = yield this.getBalances();
289
+ this.sendBalanceCache = { at: now, maps: { shielded, unshielded } };
290
+ }
291
+ const balances = this.sendBalanceCache.maps;
281
292
  return new MidnightUiTransaction.MidnightUiTransaction({
293
+ balances,
282
294
  // No on-chain dust-fee estimate is exposed yet; DUST fees are charged
283
295
  // at submit time. TODO: surface a real estimate from the wallet client.
284
296
  fetchDustFee: () => _tslib.__awaiter(this, void 0, void 0, function* () { return BigInt(0); }),
285
297
  from,
286
- // Balances come from the connector (MPC state), reusing the cached
287
- // synced wallet — never from the redcoast public-balance endpoint,
288
- // which doesn't support Midnight addresses.
289
- getShieldedBalance: () => _tslib.__awaiter(this, void 0, void 0, function* () {
290
- var _a;
291
- return (_a = (yield this.getBalances()).shielded[constants.MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
292
- }),
293
- getUnshieldedBalance: () => _tslib.__awaiter(this, void 0, void 0, function* () {
294
- var _b;
295
- return (_b = (yield this.getBalances()).unshielded[constants.MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _b !== void 0 ? _b : BigInt(0);
296
- }),
297
298
  onSubmit: (tx) => _tslib.__awaiter(this, void 0, void 0, function* () {
298
299
  if (tx.to === undefined || tx.value === undefined) {
299
300
  throw new utils.DynamicError('Recipient and amount are required before submitting');
300
301
  }
302
+ // Native NIGHT needs no tokenType; any other (shielded) token is passed
303
+ // by its ledger token type so the correct balance is debited.
304
+ const tokenType = tx.selectedTokenAddress === constants.MIDNIGHT_NATIVE_TOKEN_TYPE
305
+ ? undefined
306
+ : tx.selectedTokenAddress;
301
307
  // 3-step Midnight transfer: build → sign (ZK prove) → submit.
302
308
  const { serializedTransaction } = yield this.createTransferTransaction({
303
309
  transfers: [
304
310
  {
305
311
  amount: tx.value.toString(),
306
312
  recipientAddress: tx.to,
313
+ tokenType,
307
314
  type: tx.activePool,
308
315
  },
309
316
  ],
310
317
  });
311
318
  const finalized = yield this.signTransaction(serializedTransaction);
312
- // Return the tx hash so the Send flow advances to the success view.
313
319
  const { txHash } = yield this.submitTransaction(finalized);
320
+ // Balances changed — drop the snapshot so the next send fetches fresh.
321
+ this.clearSendBalanceCache();
322
+ // Return the tx hash so the Send flow advances to the success view.
314
323
  return txHash;
315
324
  }),
316
325
  });
@@ -441,6 +450,21 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
441
450
  });
442
451
  });
443
452
  }
453
+ /**
454
+ * One-call transfer: runs the full build → sign (ZK prove) → submit flow and
455
+ * returns the broadcast tx hash. This is what backs `wallet.sendBalance` for
456
+ * embedded Midnight wallets; use the individual steps
457
+ * (`createTransferTransaction` / `signTransaction` / `submitTransaction`) when
458
+ * you need to inspect or persist the transaction between stages.
459
+ */
460
+ createTransfer(params) {
461
+ return _tslib.__awaiter(this, void 0, void 0, function* () {
462
+ const { serializedTransaction } = yield this.createTransferTransaction(params);
463
+ const finalizedTransaction = yield this.signTransaction(serializedTransaction);
464
+ const { txHash } = yield this.submitTransaction(finalizedTransaction);
465
+ return txHash;
466
+ });
467
+ }
444
468
  /**
445
469
  * Step 1 of the Midnight transfer flow.
446
470
  * Builds an unsigned UnprovenTransaction from one or more transfer specs.
@@ -567,5 +591,7 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
567
591
  });
568
592
  }
569
593
  }
594
+ // Max age of the Send-flow balance snapshot (see `sendBalanceCache`).
595
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS = 30000;
570
596
 
571
597
  exports.DynamicWaasMidnightConnector = DynamicWaasMidnightConnector;
@@ -80,6 +80,15 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
80
80
  legacyWalletId?: string | undefined;
81
81
  password?: string | undefined;
82
82
  }): Promise<void>;
83
+ migrateFromFireblocks({ deviceId, jwt, walletPassword, ncwStorage, expectedAddresses, coinTypes, password, }: {
84
+ deviceId: string;
85
+ jwt: string;
86
+ walletPassword: string;
87
+ ncwStorage: Record<string, string>;
88
+ expectedAddresses: Record<string, string>;
89
+ coinTypes?: Record<string, number> | undefined;
90
+ password?: string | undefined;
91
+ }): Promise<import("@dynamic-labs-wallet/browser-wallet-client").MigrateFromFireblocksResponse>;
83
92
  exportPrivateKey({ accountAddress, displayContainer, password, }?: {
84
93
  accountAddress?: string | undefined;
85
94
  displayContainer?: HTMLIFrameElement | undefined;
@@ -98,6 +107,10 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
98
107
  googleDriveAccessToken?: string | undefined;
99
108
  }): Promise<void>;
100
109
  exportClientKeysharesFromGoogleDrive({ accountAddress, password, }: {
110
+ /**
111
+ * When active account is not set (e.g. primary wallet restored from session),
112
+ * fall back to the first Midnight verified credential so signing and getAddress work.
113
+ */
101
114
  accountAddress: string;
102
115
  password?: string | undefined;
103
116
  }): Promise<void>;
@@ -119,6 +132,9 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
119
132
  thresholdSignatureScheme: string;
120
133
  password?: string | undefined;
121
134
  }): Promise<void>;
135
+ /**
136
+ * Connects to the WaaS wallet client
137
+ */
122
138
  revokeDelegation({ accountAddress, password, }: {
123
139
  accountAddress: string;
124
140
  password?: string | undefined;
@@ -130,9 +146,7 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
130
146
  }): Promise<void>;
131
147
  setPassword({ accountAddress, newPassword, }: {
132
148
  accountAddress: string;
133
- newPassword: string; /**
134
- * Disconnects from the wallet (no-op for WaaS)
135
- */
149
+ newPassword: string;
136
150
  }): Promise<void>;
137
151
  signRawMessage({ accountAddress, message, password, }: {
138
152
  accountAddress: string;
@@ -180,8 +194,6 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
180
194
  /**
181
195
  * WaaS (Wallet-as-a-Service) connector for Midnight blockchain.
182
196
  * Extends the WaaS-specific Midnight base with MPC wallet functionality.
183
- *
184
- * Phase 1: Supports unshielded assets (NIGHT, MNTs) only.
185
197
  */
186
198
  export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightConnector_base implements IDynamicWaasConnector {
187
199
  ChainWallet: typeof MidnightWallet;
@@ -189,6 +201,7 @@ export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightCon
189
201
  overrideKey: string;
190
202
  isEmbeddedWallet: boolean;
191
203
  logger: Logger;
204
+ private static readonly SEND_BALANCE_TTL_MS;
192
205
  activeAccountAddress: string | undefined;
193
206
  verifiedCredentials: JwtVerifiedCredential[] | undefined;
194
207
  protected walletUiUtils: WalletUiUtils<InternalWalletConnector>;
@@ -301,6 +314,8 @@ export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightCon
301
314
  * @param from - The sender's address
302
315
  * @returns A new IUITransaction instance
303
316
  */
317
+ private sendBalanceCache;
318
+ private clearSendBalanceCache;
304
319
  createUiTransaction(from: string): Promise<IUITransaction>;
305
320
  /**
306
321
  * Returns the provider.
@@ -373,6 +388,23 @@ export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightCon
373
388
  message: string;
374
389
  registeredCount: number;
375
390
  }>;
391
+ /**
392
+ * One-call transfer: runs the full build → sign (ZK prove) → submit flow and
393
+ * returns the broadcast tx hash. This is what backs `wallet.sendBalance` for
394
+ * embedded Midnight wallets; use the individual steps
395
+ * (`createTransferTransaction` / `signTransaction` / `submitTransaction`) when
396
+ * you need to inspect or persist the transaction between stages.
397
+ */
398
+ createTransfer(params: {
399
+ transfers: Array<{
400
+ type: 'unshielded' | 'shielded';
401
+ recipientAddress: string;
402
+ amount: string;
403
+ tokenType?: string;
404
+ }>;
405
+ /** Optional TTL in minutes. Defaults to 5. */
406
+ ttlMinutes?: number;
407
+ }): Promise<string>;
376
408
  /**
377
409
  * Step 1 of the Midnight transfer flow.
378
410
  * Builds an unsigned UnprovenTransaction from one or more transfer specs.
@@ -5,7 +5,7 @@ import { DynamicError } from '@dynamic-labs/utils';
5
5
  import { withDynamicWaas } from '@dynamic-labs/waas';
6
6
  import { isSameAddress } from '@dynamic-labs/wallet-connector-core';
7
7
  import { Logger } from '@dynamic-labs/logger';
8
- import { MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE, MIDNIGHT_NATIVE_TOKEN_TYPE } from '../../constants.js';
8
+ import { MIDNIGHT_NATIVE_TOKEN_TYPE } from '../../constants.js';
9
9
  import { MidnightUiTransaction } from '../../utils/MidnightUiTransaction/MidnightUiTransaction.js';
10
10
  import { MidnightWallet } from '../../wallet/MidnightWallet.js';
11
11
  import { WaasMidnightWalletConnector } from '../WaasMidnightWalletConnector.js';
@@ -14,8 +14,6 @@ import { buildMidnightWaasConnectedApi, normalizeDustBalance, toBigIntBalanceMap
14
14
  /**
15
15
  * WaaS (Wallet-as-a-Service) connector for Midnight blockchain.
16
16
  * Extends the WaaS-specific Midnight base with MPC wallet functionality.
17
- *
18
- * Phase 1: Supports unshielded assets (NIGHT, MNTs) only.
19
17
  */
20
18
  class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletConnector) {
21
19
  constructor(props) {
@@ -85,6 +83,8 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
85
83
  */
86
84
  afterWalletSelectHook(walletAddress) {
87
85
  this.setActiveAccountAddress(walletAddress);
86
+ // Different wallet → its balances are unrelated; drop any cached snapshot.
87
+ this.clearSendBalanceCache();
88
88
  }
89
89
  /**
90
90
  * Requires and returns the signed session ID
@@ -163,7 +163,9 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
163
163
  */
164
164
  disconnect() {
165
165
  return __awaiter(this, void 0, void 0, function* () {
166
- // WaaS doesn't require explicit disconnect
166
+ // WaaS doesn't require explicit disconnect, but drop the balance snapshot
167
+ // so a shielded balance isn't retained after the wallet is disconnected.
168
+ this.clearSendBalanceCache();
167
169
  });
168
170
  }
169
171
  /**
@@ -266,47 +268,54 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
266
268
  return signature;
267
269
  });
268
270
  }
269
- /**
270
- * Creates a UI transaction for the send balance flow
271
- * @param from - The sender's address
272
- * @returns A new IUITransaction instance
273
- */
271
+ clearSendBalanceCache() {
272
+ this.sendBalanceCache = undefined;
273
+ }
274
274
  createUiTransaction(from) {
275
275
  return __awaiter(this, void 0, void 0, function* () {
276
276
  yield this.validateActiveWallet(from);
277
+ // Balances from the connector (MPC state) — never the redcoast
278
+ // public-balance endpoint, which rejects Midnight addresses. Reused from the
279
+ // short-lived cache across recreations (see field doc).
280
+ const now = Date.now();
281
+ if (!this.sendBalanceCache ||
282
+ now - this.sendBalanceCache.at >
283
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS) {
284
+ const { shielded, unshielded } = yield this.getBalances();
285
+ this.sendBalanceCache = { at: now, maps: { shielded, unshielded } };
286
+ }
287
+ const balances = this.sendBalanceCache.maps;
277
288
  return new MidnightUiTransaction({
289
+ balances,
278
290
  // No on-chain dust-fee estimate is exposed yet; DUST fees are charged
279
291
  // at submit time. TODO: surface a real estimate from the wallet client.
280
292
  fetchDustFee: () => __awaiter(this, void 0, void 0, function* () { return BigInt(0); }),
281
293
  from,
282
- // Balances come from the connector (MPC state), reusing the cached
283
- // synced wallet — never from the redcoast public-balance endpoint,
284
- // which doesn't support Midnight addresses.
285
- getShieldedBalance: () => __awaiter(this, void 0, void 0, function* () {
286
- var _a;
287
- return (_a = (yield this.getBalances()).shielded[MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
288
- }),
289
- getUnshieldedBalance: () => __awaiter(this, void 0, void 0, function* () {
290
- var _b;
291
- return (_b = (yield this.getBalances()).unshielded[MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _b !== void 0 ? _b : BigInt(0);
292
- }),
293
294
  onSubmit: (tx) => __awaiter(this, void 0, void 0, function* () {
294
295
  if (tx.to === undefined || tx.value === undefined) {
295
296
  throw new DynamicError('Recipient and amount are required before submitting');
296
297
  }
298
+ // Native NIGHT needs no tokenType; any other (shielded) token is passed
299
+ // by its ledger token type so the correct balance is debited.
300
+ const tokenType = tx.selectedTokenAddress === MIDNIGHT_NATIVE_TOKEN_TYPE
301
+ ? undefined
302
+ : tx.selectedTokenAddress;
297
303
  // 3-step Midnight transfer: build → sign (ZK prove) → submit.
298
304
  const { serializedTransaction } = yield this.createTransferTransaction({
299
305
  transfers: [
300
306
  {
301
307
  amount: tx.value.toString(),
302
308
  recipientAddress: tx.to,
309
+ tokenType,
303
310
  type: tx.activePool,
304
311
  },
305
312
  ],
306
313
  });
307
314
  const finalized = yield this.signTransaction(serializedTransaction);
308
- // Return the tx hash so the Send flow advances to the success view.
309
315
  const { txHash } = yield this.submitTransaction(finalized);
316
+ // Balances changed — drop the snapshot so the next send fetches fresh.
317
+ this.clearSendBalanceCache();
318
+ // Return the tx hash so the Send flow advances to the success view.
310
319
  return txHash;
311
320
  }),
312
321
  });
@@ -437,6 +446,21 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
437
446
  });
438
447
  });
439
448
  }
449
+ /**
450
+ * One-call transfer: runs the full build → sign (ZK prove) → submit flow and
451
+ * returns the broadcast tx hash. This is what backs `wallet.sendBalance` for
452
+ * embedded Midnight wallets; use the individual steps
453
+ * (`createTransferTransaction` / `signTransaction` / `submitTransaction`) when
454
+ * you need to inspect or persist the transaction between stages.
455
+ */
456
+ createTransfer(params) {
457
+ return __awaiter(this, void 0, void 0, function* () {
458
+ const { serializedTransaction } = yield this.createTransferTransaction(params);
459
+ const finalizedTransaction = yield this.signTransaction(serializedTransaction);
460
+ const { txHash } = yield this.submitTransaction(finalizedTransaction);
461
+ return txHash;
462
+ });
463
+ }
440
464
  /**
441
465
  * Step 1 of the Midnight transfer flow.
442
466
  * Builds an unsigned UnprovenTransaction from one or more transfer specs.
@@ -563,5 +587,7 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
563
587
  });
564
588
  }
565
589
  }
590
+ // Max age of the Send-flow balance snapshot (see `sendBalanceCache`).
591
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS = 30000;
566
592
 
567
593
  export { DynamicWaasMidnightConnector };
@@ -69,24 +69,54 @@ class MidnightWallet extends walletConnectorCore.Wallet {
69
69
  getFormattedBalances() {
70
70
  return _tslib.__awaiter(this, void 0, void 0, function* () {
71
71
  const raw = yield this._connector.getBalances();
72
- const shieldedRaw = raw.shielded[constants.MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE];
73
72
  const unshieldedRaw = raw.unshielded[constants.MIDNIGHT_NATIVE_TOKEN_TYPE];
74
73
  const dustEmpty = raw.dust.balance === BigInt(0) && raw.dust.cap === BigInt(0);
74
+ // The shielded pool holds non-NIGHT token types only, so surface a count of
75
+ // distinct tokens with a positive balance rather than a (non-existent)
76
+ // shielded NIGHT amount.
77
+ const shieldedTokenCount = Object.values(raw.shielded).filter((amount) => amount > BigInt(0)).length;
75
78
  return {
76
79
  dustBalance: dustEmpty
77
80
  ? undefined
78
81
  : formatMidnightConnectorDustBalance.formatMidnightConnectorDustBalance(raw.dust.balance, raw.dust.cap),
79
82
  // Passthrough (undefined for injected/external wallets, which omit it).
80
83
  dustSyncing: raw.dustSyncing,
81
- shieldedBalance: shieldedRaw !== undefined
82
- ? formatMidnightNativeUnshieldedBalance.formatMidnightNativeUnshieldedBalance(shieldedRaw)
83
- : undefined,
84
+ shieldedTokenCount,
84
85
  unshieldedBalance: unshieldedRaw !== undefined
85
86
  ? formatMidnightNativeUnshieldedBalance.formatMidnightNativeUnshieldedBalance(unshieldedRaw)
86
87
  : undefined,
87
88
  };
88
89
  });
89
90
  }
91
+ // Exclusive for embedded Midnight wallets
92
+ /** Builds an unsigned transaction from one or more transfer specs (step 1). */
93
+ createTransferTransaction(...args) {
94
+ return this._connector.createTransferTransaction(...args);
95
+ }
96
+ /** Signs and ZK-proves an unsigned transaction via MPC (step 2). */
97
+ signTransaction(...args) {
98
+ return this._connector.signTransaction(...args);
99
+ }
100
+ /** Broadcasts a finalized transaction (step 3). */
101
+ submitTransaction(...args) {
102
+ return this._connector.submitTransaction(...args);
103
+ }
104
+ /** Registers the wallet's NIGHT for DUST generation. */
105
+ registerDust(...args) {
106
+ return this._connector.registerDust(...args);
107
+ }
108
+ /** Releases the reserved inputs of a specific built/signed transaction. */
109
+ revertTransaction(...args) {
110
+ return this._connector.revertTransaction(...args);
111
+ }
112
+ /** Releases every finalized-but-unsubmitted transaction's reserved inputs. */
113
+ revertAllPending(...args) {
114
+ return this._connector.revertAllPending(...args);
115
+ }
116
+ /** Clears the wallet's persisted state and forces a cold re-sync. */
117
+ resetWalletCache(...args) {
118
+ return this._connector.resetWalletCache(...args);
119
+ }
90
120
  }
91
121
 
92
122
  exports.MidnightWallet = MidnightWallet;
@@ -20,6 +20,7 @@ export declare class MidnightWallet extends Wallet<MidnightWalletConnector> {
20
20
  cap: bigint;
21
21
  balance: bigint;
22
22
  };
23
+ dustSyncing?: boolean;
23
24
  }>;
24
25
  getShieldedBalance(): Promise<string | undefined>;
25
26
  getUnshieldedBalance(): Promise<string | undefined>;
@@ -28,4 +29,18 @@ export declare class MidnightWallet extends Wallet<MidnightWalletConnector> {
28
29
  cap: string;
29
30
  }>;
30
31
  getFormattedBalances(): Promise<MidnightFormattedBalances>;
32
+ /** Builds an unsigned transaction from one or more transfer specs (step 1). */
33
+ createTransferTransaction(...args: Parameters<MidnightWalletConnector['createTransferTransaction']>): ReturnType<MidnightWalletConnector['createTransferTransaction']>;
34
+ /** Signs and ZK-proves an unsigned transaction via MPC (step 2). */
35
+ signTransaction(...args: Parameters<MidnightWalletConnector['signTransaction']>): ReturnType<MidnightWalletConnector['signTransaction']>;
36
+ /** Broadcasts a finalized transaction (step 3). */
37
+ submitTransaction(...args: Parameters<MidnightWalletConnector['submitTransaction']>): ReturnType<MidnightWalletConnector['submitTransaction']>;
38
+ /** Registers the wallet's NIGHT for DUST generation. */
39
+ registerDust(...args: Parameters<MidnightWalletConnector['registerDust']>): ReturnType<MidnightWalletConnector['registerDust']>;
40
+ /** Releases the reserved inputs of a specific built/signed transaction. */
41
+ revertTransaction(...args: Parameters<MidnightWalletConnector['revertTransaction']>): ReturnType<MidnightWalletConnector['revertTransaction']>;
42
+ /** Releases every finalized-but-unsubmitted transaction's reserved inputs. */
43
+ revertAllPending(...args: Parameters<MidnightWalletConnector['revertAllPending']>): ReturnType<MidnightWalletConnector['revertAllPending']>;
44
+ /** Clears the wallet's persisted state and forces a cold re-sync. */
45
+ resetWalletCache(...args: Parameters<MidnightWalletConnector['resetWalletCache']>): ReturnType<MidnightWalletConnector['resetWalletCache']>;
31
46
  }
@@ -65,24 +65,54 @@ class MidnightWallet extends Wallet {
65
65
  getFormattedBalances() {
66
66
  return __awaiter(this, void 0, void 0, function* () {
67
67
  const raw = yield this._connector.getBalances();
68
- const shieldedRaw = raw.shielded[MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE];
69
68
  const unshieldedRaw = raw.unshielded[MIDNIGHT_NATIVE_TOKEN_TYPE];
70
69
  const dustEmpty = raw.dust.balance === BigInt(0) && raw.dust.cap === BigInt(0);
70
+ // The shielded pool holds non-NIGHT token types only, so surface a count of
71
+ // distinct tokens with a positive balance rather than a (non-existent)
72
+ // shielded NIGHT amount.
73
+ const shieldedTokenCount = Object.values(raw.shielded).filter((amount) => amount > BigInt(0)).length;
71
74
  return {
72
75
  dustBalance: dustEmpty
73
76
  ? undefined
74
77
  : formatMidnightConnectorDustBalance(raw.dust.balance, raw.dust.cap),
75
78
  // Passthrough (undefined for injected/external wallets, which omit it).
76
79
  dustSyncing: raw.dustSyncing,
77
- shieldedBalance: shieldedRaw !== undefined
78
- ? formatMidnightNativeUnshieldedBalance(shieldedRaw)
79
- : undefined,
80
+ shieldedTokenCount,
80
81
  unshieldedBalance: unshieldedRaw !== undefined
81
82
  ? formatMidnightNativeUnshieldedBalance(unshieldedRaw)
82
83
  : undefined,
83
84
  };
84
85
  });
85
86
  }
87
+ // Exclusive for embedded Midnight wallets
88
+ /** Builds an unsigned transaction from one or more transfer specs (step 1). */
89
+ createTransferTransaction(...args) {
90
+ return this._connector.createTransferTransaction(...args);
91
+ }
92
+ /** Signs and ZK-proves an unsigned transaction via MPC (step 2). */
93
+ signTransaction(...args) {
94
+ return this._connector.signTransaction(...args);
95
+ }
96
+ /** Broadcasts a finalized transaction (step 3). */
97
+ submitTransaction(...args) {
98
+ return this._connector.submitTransaction(...args);
99
+ }
100
+ /** Registers the wallet's NIGHT for DUST generation. */
101
+ registerDust(...args) {
102
+ return this._connector.registerDust(...args);
103
+ }
104
+ /** Releases the reserved inputs of a specific built/signed transaction. */
105
+ revertTransaction(...args) {
106
+ return this._connector.revertTransaction(...args);
107
+ }
108
+ /** Releases every finalized-but-unsubmitted transaction's reserved inputs. */
109
+ revertAllPending(...args) {
110
+ return this._connector.revertAllPending(...args);
111
+ }
112
+ /** Clears the wallet's persisted state and forces a cold re-sync. */
113
+ resetWalletCache(...args) {
114
+ return this._connector.resetWalletCache(...args);
115
+ }
86
116
  }
87
117
 
88
118
  export { MidnightWallet };