@dynamic-labs/midnight 4.91.1 → 4.91.3

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,40 @@
1
1
 
2
+ ### [4.91.3](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.2...v4.91.3) (2026-06-30)
3
+
4
+
5
+ ### Bug Fixes
6
+
7
+ * **bitcoin:** detect late-registering wallet-standard wallets (DYNT-1210) ([#11747](https://github.com/dynamic-labs/dynamic-auth/issues/11747)) ([17809f6](https://github.com/dynamic-labs/dynamic-auth/commit/17809f6e811fe101367cc31a2d899a8d0b0d107e))
8
+ * fetch onramp providers lazily instead of eagerly on mount ([#11767](https://github.com/dynamic-labs/dynamic-auth/issues/11767)) ([3ee6708](https://github.com/dynamic-labs/dynamic-auth/commit/3ee67087142ae1942f865e0ab37b7108700b3327))
9
+ * **iframe-setup:** sync parent URL to iframe on pushState/replaceState ([#11751](https://github.com/dynamic-labs/dynamic-auth/issues/11751)) ([c8be3af](https://github.com/dynamic-labs/dynamic-auth/commit/c8be3afc301f77b349f23398bed940ce40f93b73))
10
+ * **nonce:** fetch nonce on demand when missing before wallet verification ([#11753](https://github.com/dynamic-labs/dynamic-auth/issues/11753)) ([4f01a21](https://github.com/dynamic-labs/dynamic-auth/commit/4f01a21b6081c852f32868c8a5e04d70aaf280a2)), closes [#1](https://github.com/dynamic-labs/dynamic-auth/issues/1)
11
+ * **react-native-extension:** arm loading timer before setUrl to cover pre-onLoadStart gap ([#11764](https://github.com/dynamic-labs/dynamic-auth/issues/11764)) ([31f2015](https://github.com/dynamic-labs/dynamic-auth/commit/31f20151a4b59279f9c133b723e50de771b06436))
12
+ * **webview-controller:** unwrap ZeroDev smart wallet for native gasless ([#11758](https://github.com/dynamic-labs/dynamic-auth/issues/11758)) ([989714e](https://github.com/dynamic-labs/dynamic-auth/commit/989714e414556868c8e0c54401aac1ed7354331b))
13
+
14
+ ### [4.91.2](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.1...v4.91.2) (2026-06-26)
15
+
16
+
17
+ ### ⚠ BREAKING CHANGES
18
+
19
+ * **waas:** require expectedAddresses on migrateFromFireblocks (#11730)
20
+
21
+ ### Features
22
+
23
+ * **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))
24
+ * **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))
25
+ * **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))
26
+
27
+
28
+ ### Bug Fixes
29
+
30
+ * 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))
31
+ * 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))
32
+ * 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))
33
+ * **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))
34
+
35
+
36
+ * **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))
37
+
2
38
  ### [4.91.1](https://github.com/dynamic-labs/dynamic-auth/compare/v4.91.0...v4.91.1) (2026-06-24)
3
39
 
4
40
 
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.1";
6
+ var version = "4.91.3";
7
7
 
8
8
  exports.version = version;
package/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use client'
2
- var version = "4.91.1";
2
+ var version = "4.91.3";
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.1",
3
+ "version": "4.91.3",
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.1",
21
+ "@dynamic-labs/assert-package-version": "4.91.3",
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.1",
25
- "@dynamic-labs/types": "4.91.1",
26
- "@dynamic-labs/utils": "4.91.1",
27
- "@dynamic-labs/waas": "4.91.1",
28
- "@dynamic-labs/wallet-book": "4.91.1",
29
- "@dynamic-labs/wallet-connector-core": "4.91.1"
24
+ "@dynamic-labs/logger": "4.91.3",
25
+ "@dynamic-labs/types": "4.91.3",
26
+ "@dynamic-labs/utils": "4.91.3",
27
+ "@dynamic-labs/waas": "4.91.3",
28
+ "@dynamic-labs/wallet-book": "4.91.3",
29
+ "@dynamic-labs/wallet-connector-core": "4.91.3"
30
30
  },
31
31
  "peerDependencies": {}
32
32
  }
@@ -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() {
@@ -87,6 +87,8 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
87
87
  */
88
88
  afterWalletSelectHook(walletAddress) {
89
89
  this.setActiveAccountAddress(walletAddress);
90
+ // Different wallet → its balances are unrelated; drop any cached snapshot.
91
+ this.clearSendBalanceCache();
90
92
  }
91
93
  /**
92
94
  * Requires and returns the signed session ID
@@ -165,7 +167,9 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
165
167
  */
166
168
  disconnect() {
167
169
  return _tslib.__awaiter(this, void 0, void 0, function* () {
168
- // 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();
169
173
  });
170
174
  }
171
175
  /**
@@ -268,47 +272,54 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
268
272
  return signature;
269
273
  });
270
274
  }
271
- /**
272
- * Creates a UI transaction for the send balance flow
273
- * @param from - The sender's address
274
- * @returns A new IUITransaction instance
275
- */
275
+ clearSendBalanceCache() {
276
+ this.sendBalanceCache = undefined;
277
+ }
276
278
  createUiTransaction(from) {
277
279
  return _tslib.__awaiter(this, void 0, void 0, function* () {
278
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;
279
292
  return new MidnightUiTransaction.MidnightUiTransaction({
293
+ balances,
280
294
  // No on-chain dust-fee estimate is exposed yet; DUST fees are charged
281
295
  // at submit time. TODO: surface a real estimate from the wallet client.
282
296
  fetchDustFee: () => _tslib.__awaiter(this, void 0, void 0, function* () { return BigInt(0); }),
283
297
  from,
284
- // Balances come from the connector (MPC state), reusing the cached
285
- // synced wallet — never from the redcoast public-balance endpoint,
286
- // which doesn't support Midnight addresses.
287
- getShieldedBalance: () => _tslib.__awaiter(this, void 0, void 0, function* () {
288
- var _a;
289
- return (_a = (yield this.getBalances()).shielded[constants.MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
290
- }),
291
- getUnshieldedBalance: () => _tslib.__awaiter(this, void 0, void 0, function* () {
292
- var _b;
293
- return (_b = (yield this.getBalances()).unshielded[constants.MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _b !== void 0 ? _b : BigInt(0);
294
- }),
295
298
  onSubmit: (tx) => _tslib.__awaiter(this, void 0, void 0, function* () {
296
299
  if (tx.to === undefined || tx.value === undefined) {
297
300
  throw new utils.DynamicError('Recipient and amount are required before submitting');
298
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;
299
307
  // 3-step Midnight transfer: build → sign (ZK prove) → submit.
300
308
  const { serializedTransaction } = yield this.createTransferTransaction({
301
309
  transfers: [
302
310
  {
303
311
  amount: tx.value.toString(),
304
312
  recipientAddress: tx.to,
313
+ tokenType,
305
314
  type: tx.activePool,
306
315
  },
307
316
  ],
308
317
  });
309
318
  const finalized = yield this.signTransaction(serializedTransaction);
310
- // Return the tx hash so the Send flow advances to the success view.
311
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.
312
323
  return txHash;
313
324
  }),
314
325
  });
@@ -580,5 +591,7 @@ class DynamicWaasMidnightConnector extends waas.withDynamicWaas(WaasMidnightWall
580
591
  });
581
592
  }
582
593
  }
594
+ // Max age of the Send-flow balance snapshot (see `sendBalanceCache`).
595
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS = 30000;
583
596
 
584
597
  exports.DynamicWaasMidnightConnector = DynamicWaasMidnightConnector;
@@ -67,11 +67,6 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
67
67
  password?: string | undefined;
68
68
  bitcoinConfig?: import("@dynamic-labs-wallet/browser-wallet-client").BitcoinConfig | undefined;
69
69
  } | undefined): Promise<{
70
- /**
71
- * Requires and returns the signed session ID
72
- * @returns The signed session ID
73
- * @throws {DynamicError} If signed session ID is not available
74
- */
75
70
  chainName: string;
76
71
  accountAddress: string;
77
72
  publicKeyHex: string;
@@ -82,15 +77,18 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
82
77
  thresholdSignatureScheme?: string | undefined;
83
78
  publicAddressCheck?: string | undefined;
84
79
  addressType?: string | undefined;
85
- /**
86
- * Validates that the expected wallet address matches the active wallet
87
- * Updates the active account address if it doesn't match
88
- * @param expectedAddress - The expected wallet address to validate
89
- * @throws {DynamicError} If signed session ID is not available or account is not found
90
- */
91
80
  legacyWalletId?: string | undefined;
92
81
  password?: string | undefined;
93
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>;
94
92
  exportPrivateKey({ accountAddress, displayContainer, password, }?: {
95
93
  accountAddress?: string | undefined;
96
94
  displayContainer?: HTMLIFrameElement | undefined;
@@ -109,6 +107,10 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
109
107
  googleDriveAccessToken?: string | undefined;
110
108
  }): Promise<void>;
111
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
+ */
112
114
  accountAddress: string;
113
115
  password?: string | undefined;
114
116
  }): Promise<void>;
@@ -130,14 +132,15 @@ declare const DynamicWaasMidnightConnector_base: (abstract new (...args: any[])
130
132
  thresholdSignatureScheme: string;
131
133
  password?: string | undefined;
132
134
  }): Promise<void>;
135
+ /**
136
+ * Connects to the WaaS wallet client
137
+ */
133
138
  revokeDelegation({ accountAddress, password, }: {
134
139
  accountAddress: string;
135
140
  password?: string | undefined;
136
141
  }): Promise<void>;
137
142
  updatePassword({ accountAddress, existingPassword, newPassword, }: {
138
- accountAddress: string; /**
139
- * Connects to the WaaS wallet client
140
- */
143
+ accountAddress: string;
141
144
  existingPassword?: string | undefined;
142
145
  newPassword: string;
143
146
  }): Promise<void>;
@@ -198,6 +201,7 @@ export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightCon
198
201
  overrideKey: string;
199
202
  isEmbeddedWallet: boolean;
200
203
  logger: Logger;
204
+ private static readonly SEND_BALANCE_TTL_MS;
201
205
  activeAccountAddress: string | undefined;
202
206
  verifiedCredentials: JwtVerifiedCredential[] | undefined;
203
207
  protected walletUiUtils: WalletUiUtils<InternalWalletConnector>;
@@ -310,6 +314,8 @@ export declare class DynamicWaasMidnightConnector extends DynamicWaasMidnightCon
310
314
  * @param from - The sender's address
311
315
  * @returns A new IUITransaction instance
312
316
  */
317
+ private sendBalanceCache;
318
+ private clearSendBalanceCache;
313
319
  createUiTransaction(from: string): Promise<IUITransaction>;
314
320
  /**
315
321
  * Returns the provider.
@@ -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';
@@ -83,6 +83,8 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
83
83
  */
84
84
  afterWalletSelectHook(walletAddress) {
85
85
  this.setActiveAccountAddress(walletAddress);
86
+ // Different wallet → its balances are unrelated; drop any cached snapshot.
87
+ this.clearSendBalanceCache();
86
88
  }
87
89
  /**
88
90
  * Requires and returns the signed session ID
@@ -161,7 +163,9 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
161
163
  */
162
164
  disconnect() {
163
165
  return __awaiter(this, void 0, void 0, function* () {
164
- // 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();
165
169
  });
166
170
  }
167
171
  /**
@@ -264,47 +268,54 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
264
268
  return signature;
265
269
  });
266
270
  }
267
- /**
268
- * Creates a UI transaction for the send balance flow
269
- * @param from - The sender's address
270
- * @returns A new IUITransaction instance
271
- */
271
+ clearSendBalanceCache() {
272
+ this.sendBalanceCache = undefined;
273
+ }
272
274
  createUiTransaction(from) {
273
275
  return __awaiter(this, void 0, void 0, function* () {
274
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;
275
288
  return new MidnightUiTransaction({
289
+ balances,
276
290
  // No on-chain dust-fee estimate is exposed yet; DUST fees are charged
277
291
  // at submit time. TODO: surface a real estimate from the wallet client.
278
292
  fetchDustFee: () => __awaiter(this, void 0, void 0, function* () { return BigInt(0); }),
279
293
  from,
280
- // Balances come from the connector (MPC state), reusing the cached
281
- // synced wallet — never from the redcoast public-balance endpoint,
282
- // which doesn't support Midnight addresses.
283
- getShieldedBalance: () => __awaiter(this, void 0, void 0, function* () {
284
- var _a;
285
- return (_a = (yield this.getBalances()).shielded[MIDNIGHT_NATIVE_SHIELDED_TOKEN_TYPE]) !== null && _a !== void 0 ? _a : BigInt(0);
286
- }),
287
- getUnshieldedBalance: () => __awaiter(this, void 0, void 0, function* () {
288
- var _b;
289
- return (_b = (yield this.getBalances()).unshielded[MIDNIGHT_NATIVE_TOKEN_TYPE]) !== null && _b !== void 0 ? _b : BigInt(0);
290
- }),
291
294
  onSubmit: (tx) => __awaiter(this, void 0, void 0, function* () {
292
295
  if (tx.to === undefined || tx.value === undefined) {
293
296
  throw new DynamicError('Recipient and amount are required before submitting');
294
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;
295
303
  // 3-step Midnight transfer: build → sign (ZK prove) → submit.
296
304
  const { serializedTransaction } = yield this.createTransferTransaction({
297
305
  transfers: [
298
306
  {
299
307
  amount: tx.value.toString(),
300
308
  recipientAddress: tx.to,
309
+ tokenType,
301
310
  type: tx.activePool,
302
311
  },
303
312
  ],
304
313
  });
305
314
  const finalized = yield this.signTransaction(serializedTransaction);
306
- // Return the tx hash so the Send flow advances to the success view.
307
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.
308
319
  return txHash;
309
320
  }),
310
321
  });
@@ -576,5 +587,7 @@ class DynamicWaasMidnightConnector extends withDynamicWaas(WaasMidnightWalletCon
576
587
  });
577
588
  }
578
589
  }
590
+ // Max age of the Send-flow balance snapshot (see `sendBalanceCache`).
591
+ DynamicWaasMidnightConnector.SEND_BALANCE_TTL_MS = 30000;
579
592
 
580
593
  export { DynamicWaasMidnightConnector };
@@ -69,18 +69,19 @@ 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,
@@ -65,18 +65,19 @@ 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,