@dynamic-labs/sdk-react-core 4.84.0 → 4.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/package.cjs +1 -1
- package/package.js +1 -1
- package/package.json +12 -12
- package/src/index.cjs +2 -0
- package/src/index.d.ts +2 -2
- package/src/index.js +1 -0
- package/src/lib/components/SendBalanceForm/SendBalanceForm.cjs +26 -1
- package/src/lib/components/SendBalanceForm/SendBalanceForm.js +26 -1
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.cjs +6 -1
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.js +6 -1
- package/src/lib/utils/hooks/index.d.ts +2 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.cjs +24 -4
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.d.ts +10 -2
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.js +24 -4
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +14 -3
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +5 -1
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +14 -3
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +14 -0
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +14 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/index.d.ts +2 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.cjs +19 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.d.ts +9 -0
- package/src/lib/utils/hooks/usePrivateTokenBalances/usePrivateTokenBalances.js +15 -0
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.cjs +19 -16
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.d.ts +8 -0
- package/src/lib/utils/hooks/useWalletDelegation/useWalletDelegation.js +19 -17
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +138 -21
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +139 -22
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.cjs +134 -0
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.d.ts +69 -0
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/optimisticShield.js +127 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { useAleoShieldedBalances } from '../useAleoShieldedBalances/useAleoShieldedBalances.js';
|
|
3
|
+
|
|
4
|
+
const usePrivateTokenBalances = () => {
|
|
5
|
+
const { tokenBalances, isLoading, error, refetch, supportsShielded } = useAleoShieldedBalances();
|
|
6
|
+
return {
|
|
7
|
+
error,
|
|
8
|
+
isLoading,
|
|
9
|
+
refetch,
|
|
10
|
+
supportsPrivateBalances: supportsShielded,
|
|
11
|
+
tokenBalances,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { usePrivateTokenBalances };
|
|
@@ -41,6 +41,14 @@ var useDynamicWaas = require('../useDynamicWaas/useDynamicWaas.cjs');
|
|
|
41
41
|
var useRefreshAuth = require('../useRefreshAuth/useRefreshAuth.cjs');
|
|
42
42
|
var DelegationError = require('./DelegationError.cjs');
|
|
43
43
|
|
|
44
|
+
// Pre-share-set backends mark delegation via keyShares[].backupLocation='delegated';
|
|
45
|
+
// share-set backends use otherShareSets[].shareSetType='delegated' instead.
|
|
46
|
+
const isWalletDelegated = (walletProperties) => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const hasDelegatedKeyShare = (_a = walletProperties === null || walletProperties === void 0 ? void 0 : walletProperties.keyShares) === null || _a === void 0 ? void 0 : _a.some((keyShare) => keyShare.backupLocation === 'delegated');
|
|
49
|
+
const hasDelegatedShareSet = (_b = walletProperties === null || walletProperties === void 0 ? void 0 : walletProperties.otherShareSets) === null || _b === void 0 ? void 0 : _b.some((shareSet) => shareSet.shareSetType === 'delegated');
|
|
50
|
+
return Boolean(hasDelegatedKeyShare || hasDelegatedShareSet);
|
|
51
|
+
};
|
|
44
52
|
/**
|
|
45
53
|
* Gets pending wallets that are eligible for delegation.
|
|
46
54
|
* If wallets are provided and not empty, converts them to WalletWithStatus[] with pending status.
|
|
@@ -87,15 +95,11 @@ const useWalletDelegation = () => {
|
|
|
87
95
|
const waasCredentials = user.verifiedCredentials.filter((vc) => vc.walletName === 'dynamicwaas' &&
|
|
88
96
|
vc.format === sdkApiCore.JwtVerifiedCredentialFormatEnum.Blockchain);
|
|
89
97
|
const hasWalletNeedingDelegation = waasCredentials.some((vc) => {
|
|
90
|
-
var _a, _b, _c
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Check if dismissed this session
|
|
96
|
-
const isDismissedThisSession = (_e = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _e === void 0 ? void 0 : _e.includes(vc.id);
|
|
97
|
-
// Needs delegation if: not delegated, not denied, and not dismissed this session
|
|
98
|
-
return (!hasDelegatedKeyShare && !hasDeniedAccess && !isDismissedThisSession);
|
|
98
|
+
var _a, _b, _c;
|
|
99
|
+
const isDelegated = isWalletDelegated(vc.walletProperties);
|
|
100
|
+
const hasDeniedAccess = ((_b = (_a = vc.walletProperties) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.hasDeniedDelegatedAccess) === true;
|
|
101
|
+
const isDismissedThisSession = (_c = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _c === void 0 ? void 0 : _c.includes(vc.id);
|
|
102
|
+
return !isDelegated && !hasDeniedAccess && !isDismissedThisSession;
|
|
99
103
|
});
|
|
100
104
|
return hasWalletNeedingDelegation;
|
|
101
105
|
}, [user === null || user === void 0 ? void 0 : user.verifiedCredentials, delegatedAccessEnabled, promptUsersOnSignIn]);
|
|
@@ -129,20 +133,18 @@ const useWalletDelegation = () => {
|
|
|
129
133
|
// Map credentials to wallets with status
|
|
130
134
|
return waasCredentials
|
|
131
135
|
.map((vc) => {
|
|
132
|
-
var _a, _b, _c
|
|
136
|
+
var _a, _b, _c;
|
|
133
137
|
let status = 'pending';
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
const hasDeniedAccess = ((_d = (_c = vc.walletProperties) === null || _c === void 0 ? void 0 : _c.settings) === null || _d === void 0 ? void 0 : _d.hasDeniedDelegatedAccess) === true;
|
|
138
|
-
if (hasDelegatedKeyShare) {
|
|
138
|
+
const isDelegated = isWalletDelegated(vc.walletProperties);
|
|
139
|
+
const hasDeniedAccess = ((_b = (_a = vc.walletProperties) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.hasDeniedDelegatedAccess) === true;
|
|
140
|
+
if (isDelegated) {
|
|
139
141
|
status = 'delegated';
|
|
140
142
|
}
|
|
141
143
|
else if (hasDeniedAccess) {
|
|
142
144
|
status = 'denied';
|
|
143
145
|
}
|
|
144
146
|
// Check if dismissed this session (UI state only)
|
|
145
|
-
const isDismissedThisSession = (
|
|
147
|
+
const isDismissedThisSession = (_c = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _c === void 0 ? void 0 : _c.includes(vc.id);
|
|
146
148
|
// Find corresponding wallet from userWallets
|
|
147
149
|
const wallet = userWallets.find((w) => w.address === vc.address);
|
|
148
150
|
if (!wallet) {
|
|
@@ -309,4 +311,5 @@ const useWalletDelegation = () => {
|
|
|
309
311
|
};
|
|
310
312
|
|
|
311
313
|
exports.getWalletsToDelegate = getWalletsToDelegate;
|
|
314
|
+
exports.isWalletDelegated = isWalletDelegated;
|
|
312
315
|
exports.useWalletDelegation = useWalletDelegation;
|
|
@@ -5,6 +5,14 @@ export type WalletWithStatus = Wallet & {
|
|
|
5
5
|
status: WalletDelegationStatus;
|
|
6
6
|
isDismissedThisSession?: boolean;
|
|
7
7
|
};
|
|
8
|
+
export declare const isWalletDelegated: (walletProperties: {
|
|
9
|
+
keyShares?: Array<{
|
|
10
|
+
backupLocation?: string;
|
|
11
|
+
}>;
|
|
12
|
+
otherShareSets?: Array<{
|
|
13
|
+
shareSetType?: string;
|
|
14
|
+
}>;
|
|
15
|
+
} | null | undefined) => boolean;
|
|
8
16
|
/**
|
|
9
17
|
* Gets pending wallets that are eligible for delegation.
|
|
10
18
|
* If wallets are provided and not empty, converts them to WalletWithStatus[] with pending status.
|
|
@@ -37,6 +37,14 @@ import { useDynamicWaas } from '../useDynamicWaas/useDynamicWaas.js';
|
|
|
37
37
|
import { useRefreshAuth } from '../useRefreshAuth/useRefreshAuth.js';
|
|
38
38
|
import { DelegationError } from './DelegationError.js';
|
|
39
39
|
|
|
40
|
+
// Pre-share-set backends mark delegation via keyShares[].backupLocation='delegated';
|
|
41
|
+
// share-set backends use otherShareSets[].shareSetType='delegated' instead.
|
|
42
|
+
const isWalletDelegated = (walletProperties) => {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const hasDelegatedKeyShare = (_a = walletProperties === null || walletProperties === void 0 ? void 0 : walletProperties.keyShares) === null || _a === void 0 ? void 0 : _a.some((keyShare) => keyShare.backupLocation === 'delegated');
|
|
45
|
+
const hasDelegatedShareSet = (_b = walletProperties === null || walletProperties === void 0 ? void 0 : walletProperties.otherShareSets) === null || _b === void 0 ? void 0 : _b.some((shareSet) => shareSet.shareSetType === 'delegated');
|
|
46
|
+
return Boolean(hasDelegatedKeyShare || hasDelegatedShareSet);
|
|
47
|
+
};
|
|
40
48
|
/**
|
|
41
49
|
* Gets pending wallets that are eligible for delegation.
|
|
42
50
|
* If wallets are provided and not empty, converts them to WalletWithStatus[] with pending status.
|
|
@@ -83,15 +91,11 @@ const useWalletDelegation = () => {
|
|
|
83
91
|
const waasCredentials = user.verifiedCredentials.filter((vc) => vc.walletName === 'dynamicwaas' &&
|
|
84
92
|
vc.format === JwtVerifiedCredentialFormatEnum.Blockchain);
|
|
85
93
|
const hasWalletNeedingDelegation = waasCredentials.some((vc) => {
|
|
86
|
-
var _a, _b, _c
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Check if dismissed this session
|
|
92
|
-
const isDismissedThisSession = (_e = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _e === void 0 ? void 0 : _e.includes(vc.id);
|
|
93
|
-
// Needs delegation if: not delegated, not denied, and not dismissed this session
|
|
94
|
-
return (!hasDelegatedKeyShare && !hasDeniedAccess && !isDismissedThisSession);
|
|
94
|
+
var _a, _b, _c;
|
|
95
|
+
const isDelegated = isWalletDelegated(vc.walletProperties);
|
|
96
|
+
const hasDeniedAccess = ((_b = (_a = vc.walletProperties) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.hasDeniedDelegatedAccess) === true;
|
|
97
|
+
const isDismissedThisSession = (_c = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _c === void 0 ? void 0 : _c.includes(vc.id);
|
|
98
|
+
return !isDelegated && !hasDeniedAccess && !isDismissedThisSession;
|
|
95
99
|
});
|
|
96
100
|
return hasWalletNeedingDelegation;
|
|
97
101
|
}, [user === null || user === void 0 ? void 0 : user.verifiedCredentials, delegatedAccessEnabled, promptUsersOnSignIn]);
|
|
@@ -125,20 +129,18 @@ const useWalletDelegation = () => {
|
|
|
125
129
|
// Map credentials to wallets with status
|
|
126
130
|
return waasCredentials
|
|
127
131
|
.map((vc) => {
|
|
128
|
-
var _a, _b, _c
|
|
132
|
+
var _a, _b, _c;
|
|
129
133
|
let status = 'pending';
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
const hasDeniedAccess = ((_d = (_c = vc.walletProperties) === null || _c === void 0 ? void 0 : _c.settings) === null || _d === void 0 ? void 0 : _d.hasDeniedDelegatedAccess) === true;
|
|
134
|
-
if (hasDelegatedKeyShare) {
|
|
134
|
+
const isDelegated = isWalletDelegated(vc.walletProperties);
|
|
135
|
+
const hasDeniedAccess = ((_b = (_a = vc.walletProperties) === null || _a === void 0 ? void 0 : _a.settings) === null || _b === void 0 ? void 0 : _b.hasDeniedDelegatedAccess) === true;
|
|
136
|
+
if (isDelegated) {
|
|
135
137
|
status = 'delegated';
|
|
136
138
|
}
|
|
137
139
|
else if (hasDeniedAccess) {
|
|
138
140
|
status = 'denied';
|
|
139
141
|
}
|
|
140
142
|
// Check if dismissed this session (UI state only)
|
|
141
|
-
const isDismissedThisSession = (
|
|
143
|
+
const isDismissedThisSession = (_c = sessionState === null || sessionState === void 0 ? void 0 : sessionState.dismissedWallets) === null || _c === void 0 ? void 0 : _c.includes(vc.id);
|
|
142
144
|
// Find corresponding wallet from userWallets
|
|
143
145
|
const wallet = userWallets.find((w) => w.address === vc.address);
|
|
144
146
|
if (!wallet) {
|
|
@@ -304,4 +306,4 @@ const useWalletDelegation = () => {
|
|
|
304
306
|
};
|
|
305
307
|
};
|
|
306
308
|
|
|
307
|
-
export { getWalletsToDelegate, useWalletDelegation };
|
|
309
|
+
export { getWalletsToDelegate, isWalletDelegated, useWalletDelegation };
|
package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs
CHANGED
|
@@ -127,6 +127,7 @@ require('../../views/ReceiveWalletFunds/ReceiveWalletFunds.cjs');
|
|
|
127
127
|
require('../../../../store/state/multichainBalances.cjs');
|
|
128
128
|
require('@dynamic-labs/store');
|
|
129
129
|
require('../../../../shared/utils/functions/getInitialUrl/getInitialUrl.cjs');
|
|
130
|
+
var optimisticShield = require('./optimisticShield.cjs');
|
|
130
131
|
var TokenBalanceList = require('./TokenBalanceList/TokenBalanceList.cjs');
|
|
131
132
|
|
|
132
133
|
/** Component to display token balances for the primary wallet */
|
|
@@ -153,6 +154,57 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
153
154
|
includeNativeBalance: true,
|
|
154
155
|
});
|
|
155
156
|
const { tokenBalances: shieldedTokenBalances, isLoading: isLoadingShielded, refetch: refetchShielded, supportsShielded, } = useAleoShieldedBalances.useAleoShieldedBalances();
|
|
157
|
+
// Optimistic shield ledger. A shield broadcast resolves before the
|
|
158
|
+
// public-balance mapping flips and well before the RecordScanner
|
|
159
|
+
// index reflects the new private record, so naively trusting the
|
|
160
|
+
// server feed shows a stale unshielded balance and an unchanged
|
|
161
|
+
// shielded balance for ~10–90s after the user clicks. Pushing an
|
|
162
|
+
// entry here deducts the burned amount from unshielded and prepends
|
|
163
|
+
// / bumps the matching shielded row, then `reconcileOptimisticShields`
|
|
164
|
+
// drops the entry as soon as either server feed catches up.
|
|
165
|
+
const [optimisticShields, setOptimisticShields] = React.useState([]);
|
|
166
|
+
const effectiveUnshieldedTokenBalances = React.useMemo(() => optimisticShield.applyOptimisticUnshieldedDeductions(unshieldedTokenBalances !== null && unshieldedTokenBalances !== void 0 ? unshieldedTokenBalances : [], optimisticShields), [unshieldedTokenBalances, optimisticShields]);
|
|
167
|
+
const effectiveShieldedTokenBalances = React.useMemo(() => optimisticShield.applyOptimisticShieldedAdditions(shieldedTokenBalances, optimisticShields), [shieldedTokenBalances, optimisticShields]);
|
|
168
|
+
// Reconcile + GC optimistic entries whenever either server feed
|
|
169
|
+
// updates. The reconciler is pure; we short-circuit the state write
|
|
170
|
+
// when nothing changed so downstream memos don't churn on every
|
|
171
|
+
// refresh tick.
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
setOptimisticShields((current) => {
|
|
174
|
+
if (current.length === 0)
|
|
175
|
+
return current;
|
|
176
|
+
const next = optimisticShield.reconcileOptimisticShields(current, unshieldedTokenBalances !== null && unshieldedTokenBalances !== void 0 ? unshieldedTokenBalances : [], shieldedTokenBalances, Date.now());
|
|
177
|
+
return next.length === current.length ? current : next;
|
|
178
|
+
});
|
|
179
|
+
}, [unshieldedTokenBalances, shieldedTokenBalances]);
|
|
180
|
+
// Push helper used by both the manual Shield Manually CTA and the
|
|
181
|
+
// auto-shield hook so the optimistic UI semantics are identical
|
|
182
|
+
// whichever flow dispatches the broadcast.
|
|
183
|
+
const recordOptimisticShield = React.useCallback((info) => {
|
|
184
|
+
const { token, amount } = info;
|
|
185
|
+
const matchingShielded = shieldedTokenBalances.find((t) => token.isNative
|
|
186
|
+
? Boolean(t.isNative)
|
|
187
|
+
: !t.isNative && t.address === token.address);
|
|
188
|
+
setOptimisticShields((current) => {
|
|
189
|
+
var _a, _b, _c, _d;
|
|
190
|
+
return [
|
|
191
|
+
...current,
|
|
192
|
+
{
|
|
193
|
+
amount,
|
|
194
|
+
createdAtMs: Date.now(),
|
|
195
|
+
decimals: (_a = token.decimals) !== null && _a !== void 0 ? _a : 0,
|
|
196
|
+
isNative: Boolean(token.isNative),
|
|
197
|
+
logoURI: (_b = token.logoURI) !== null && _b !== void 0 ? _b : '',
|
|
198
|
+
name: token.name,
|
|
199
|
+
preShieldShieldedRaw: BigInt(Math.round((_c = matchingShielded === null || matchingShielded === void 0 ? void 0 : matchingShielded.rawBalance) !== null && _c !== void 0 ? _c : 0)),
|
|
200
|
+
preShieldUnshieldedRaw: BigInt(Math.round((_d = token.rawBalance) !== null && _d !== void 0 ? _d : 0)),
|
|
201
|
+
price: token.price,
|
|
202
|
+
symbol: token.symbol,
|
|
203
|
+
tokenAddress: token.address,
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
});
|
|
207
|
+
}, [shieldedTokenBalances]);
|
|
156
208
|
/**
|
|
157
209
|
* Tab state for chains that have a shielded/unshielded split. Defaults to
|
|
158
210
|
* shielded so Aleo wallets land on their primary balance type. Other
|
|
@@ -197,11 +249,22 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
197
249
|
// Manually CTA, which prompts the user-paid confirmation modal.
|
|
198
250
|
const { isShielding: isAutoShielding, currentlyShieldingTokenKeys: autoShieldingTokenKeys, } = useAleoAutoShieldSponsoredTokens.useAleoAutoShieldSponsoredTokens({
|
|
199
251
|
accountAddress: primaryWallet === null || primaryWallet === void 0 ? void 0 : primaryWallet.address,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
252
|
+
onShieldDispatched: recordOptimisticShield,
|
|
253
|
+
onShielded: React.useCallback(
|
|
254
|
+
// Parallel refresh — same reasoning as the manual shield path:
|
|
255
|
+
// shield ops move value across both tabs, and serializing the
|
|
256
|
+
// two refreshes doubled the visible latency for the Shielded
|
|
257
|
+
// tab in the common case.
|
|
258
|
+
() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
259
|
+
return Promise.all([fetchAccountBalances(true), refetchShielded()]).then(() => undefined);
|
|
203
260
|
}), [fetchAccountBalances, refetchShielded]),
|
|
204
261
|
shieldHandle: aleoShieldHandle,
|
|
262
|
+
// Auto-shield reads the raw server feed (not the optimistic one):
|
|
263
|
+
// once we deduct optimistically the to-be-shielded token drops to
|
|
264
|
+
// zero in the effective list, which would suppress the next
|
|
265
|
+
// auto-shield run before this one finishes. The auto-shield hook
|
|
266
|
+
// has its own per-session idempotency (`seenKeys`) so it won't
|
|
267
|
+
// re-fire for the same token anyway.
|
|
205
268
|
unshieldedTokenBalances: unshieldedTokenBalances,
|
|
206
269
|
});
|
|
207
270
|
// Liveness handle for the manual post-shield poll — set to true when
|
|
@@ -214,12 +277,25 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
214
277
|
isUnmountedRef.current = true;
|
|
215
278
|
};
|
|
216
279
|
}, []);
|
|
280
|
+
// Latest server-side unshielded list mirrored into a ref so the
|
|
281
|
+
// post-shield convergence check can read the freshest value after
|
|
282
|
+
// each polled refresh. `unshieldedTokenBalances` is closure-captured
|
|
283
|
+
// by `handleShieldToken` at callback construction time, so without
|
|
284
|
+
// this ref the predicate would read the pre-shield list forever and
|
|
285
|
+
// the poll would never exit early. Reads from the raw server feed,
|
|
286
|
+
// NOT the optimistic one — convergence means "on-chain effect
|
|
287
|
+
// observed", which is only meaningful against the actual server
|
|
288
|
+
// state.
|
|
289
|
+
const unshieldedTokenBalancesRef = React.useRef(unshieldedTokenBalances);
|
|
290
|
+
React.useEffect(() => {
|
|
291
|
+
unshieldedTokenBalancesRef.current = unshieldedTokenBalances;
|
|
292
|
+
}, [unshieldedTokenBalances]);
|
|
217
293
|
// Token currently awaiting user-paid fee confirmation. Set when
|
|
218
294
|
// Shield Manually is clicked on a token Feemaster doesn't sponsor;
|
|
219
295
|
// cleared on Cancel or after the modal's Shield button dispatches.
|
|
220
296
|
const [pendingShieldToken, setPendingShieldToken] = React.useState(null);
|
|
221
297
|
const handleShieldToken = React.useCallback((token) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
-
var _f;
|
|
298
|
+
var _f, _g;
|
|
223
299
|
if (!aleoShieldHandle || shieldingAddress)
|
|
224
300
|
return;
|
|
225
301
|
// `rawBalance` is the atomic-units count redcoast surfaces (number).
|
|
@@ -229,6 +305,14 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
229
305
|
const atomic = BigInt(Math.round((_f = token.rawBalance) !== null && _f !== void 0 ? _f : 0));
|
|
230
306
|
if (atomic <= BigInt(0))
|
|
231
307
|
return;
|
|
308
|
+
// Snapshot the pre-shield unshielded balance for the convergence
|
|
309
|
+
// predicate below. Aleo's `transfer_public_to_private` burns the
|
|
310
|
+
// public mapping value, so a successful shield must show up as a
|
|
311
|
+
// drop in this token's `rawBalance`. Polling exits early once
|
|
312
|
+
// that drop materializes — no need to keep refreshing on the
|
|
313
|
+
// long-tail schedule when we've already observed the on-chain
|
|
314
|
+
// effect.
|
|
315
|
+
const preShieldRawBalance = (_g = token.rawBalance) !== null && _g !== void 0 ? _g : 0;
|
|
232
316
|
setShieldingAddress(token.address);
|
|
233
317
|
let didBroadcast = false;
|
|
234
318
|
try {
|
|
@@ -238,11 +322,22 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
238
322
|
tokenAddress: token.address,
|
|
239
323
|
});
|
|
240
324
|
didBroadcast = true;
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
325
|
+
// Optimistic balance update: deduct the burned amount from
|
|
326
|
+
// unshielded and bump (or synthesise) the matching shielded
|
|
327
|
+
// row before the server-side feeds catch up. Without this the
|
|
328
|
+
// Shield Manually CTA flickers back into view during the
|
|
329
|
+
// indexing tail and a fast user can issue a second shield
|
|
330
|
+
// that fails server-side (validator rejects the now-zero
|
|
331
|
+
// public balance) and looks to them like the first one
|
|
332
|
+
// failed.
|
|
333
|
+
recordOptimisticShield({ amount: atomic, token });
|
|
334
|
+
// Refresh both tabs in parallel. Shield moves value across the
|
|
335
|
+
// shielded/unshielded boundary, so both lists need to
|
|
336
|
+
// converge. Running them in parallel (vs. sequential) halves
|
|
337
|
+
// the visible refresh latency and shortens the window where
|
|
338
|
+
// `useTokenBalances`' `isLoading` guard could drop a
|
|
339
|
+
// concurrent fetch.
|
|
340
|
+
yield Promise.all([fetchAccountBalances(true), refetchShielded()]);
|
|
246
341
|
}
|
|
247
342
|
catch (err) {
|
|
248
343
|
logger.logger.debug('[ActiveWalletBalance] shieldToken failed', err);
|
|
@@ -253,7 +348,7 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
253
348
|
// The relay-accepted broadcast resolves *before* the
|
|
254
349
|
// public→private transition finalizes on-chain, so the immediate
|
|
255
350
|
// refresh above sees the pre-confirmation balance. Re-poll on the
|
|
256
|
-
// shared backoff so the Unshielded row visibly drops
|
|
351
|
+
// shared backoff so the Unshielded row visibly drops and the
|
|
257
352
|
// Shielded row picks the new record up without a manual reload.
|
|
258
353
|
// Polling is best-effort — errors are swallowed inside
|
|
259
354
|
// `pollOnShielded`. Skip when the broadcast itself failed; there's
|
|
@@ -261,10 +356,27 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
261
356
|
if (!didBroadcast)
|
|
262
357
|
return;
|
|
263
358
|
yield pollOnShielded.pollOnShielded(() => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
264
|
-
yield fetchAccountBalances(true);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
359
|
+
yield Promise.all([fetchAccountBalances(true), refetchShielded()]);
|
|
360
|
+
}), () => isUnmountedRef.current, () => {
|
|
361
|
+
var _a, _b;
|
|
362
|
+
// Convergence: the just-shielded token's unshielded raw
|
|
363
|
+
// balance has dropped below the pre-shield value. We read
|
|
364
|
+
// via the ref so we see whatever the store published after
|
|
365
|
+
// the most recent poll, not the closure-captured list from
|
|
366
|
+
// callback construction time. We don't require an exact 0
|
|
367
|
+
// — additional unshielded credits could land between scans;
|
|
368
|
+
// any decrease is sufficient evidence the burn took effect.
|
|
369
|
+
const current = (_a = unshieldedTokenBalancesRef.current) === null || _a === void 0 ? void 0 : _a.find((t) => t.address === token.address && t.isNative === token.isNative);
|
|
370
|
+
const currentRaw = (_b = current === null || current === void 0 ? void 0 : current.rawBalance) !== null && _b !== void 0 ? _b : 0;
|
|
371
|
+
return currentRaw < preShieldRawBalance;
|
|
372
|
+
});
|
|
373
|
+
}), [
|
|
374
|
+
aleoShieldHandle,
|
|
375
|
+
fetchAccountBalances,
|
|
376
|
+
recordOptimisticShield,
|
|
377
|
+
refetchShielded,
|
|
378
|
+
shieldingAddress,
|
|
379
|
+
]);
|
|
268
380
|
const getSecondaryAction = React.useCallback((token) => {
|
|
269
381
|
// Only on the Unshielded tab, only when the connector exposes shield
|
|
270
382
|
// helpers, only for tokens registered as shieldable, only for tokens
|
|
@@ -327,12 +439,12 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
327
439
|
shieldingAddress,
|
|
328
440
|
]);
|
|
329
441
|
const tokenBalances = React.useMemo(() => supportsShielded && activeShieldTab === 'shielded'
|
|
330
|
-
?
|
|
331
|
-
:
|
|
442
|
+
? effectiveShieldedTokenBalances
|
|
443
|
+
: effectiveUnshieldedTokenBalances, [
|
|
332
444
|
activeShieldTab,
|
|
333
|
-
|
|
445
|
+
effectiveShieldedTokenBalances,
|
|
446
|
+
effectiveUnshieldedTokenBalances,
|
|
334
447
|
supportsShielded,
|
|
335
|
-
unshieldedTokenBalances,
|
|
336
448
|
]);
|
|
337
449
|
const filteredTokenBalances = React.useMemo(() => (tokenBalances === null || tokenBalances === void 0 ? void 0 : tokenBalances.filter((token) => token.name)) || [], [tokenBalances]);
|
|
338
450
|
const totalValue = React.useMemo(() => filteredTokenBalances.reduce((acc, token) => acc + ((token === null || token === void 0 ? void 0 : token.marketValue) || 0), 0), [filteredTokenBalances]);
|
|
@@ -375,8 +487,14 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
375
487
|
setIsRefreshing(true);
|
|
376
488
|
setIsSuccess(false);
|
|
377
489
|
try {
|
|
378
|
-
|
|
379
|
-
|
|
490
|
+
// Chains with a shielded/unshielded split (Aleo) refresh BOTH
|
|
491
|
+
// tabs on a single click. A shield op moves value across the
|
|
492
|
+
// boundary, so refreshing only the active tab leaves the other
|
|
493
|
+
// visibly stale — the original complaint that drove this fix.
|
|
494
|
+
// Other chains never had `refetchShielded` (no-op fallback) so
|
|
495
|
+
// they still just hit `fetchAccountBalances` here.
|
|
496
|
+
if (supportsShielded) {
|
|
497
|
+
yield Promise.all([refetchShielded(), fetchAccountBalances(true)]);
|
|
380
498
|
}
|
|
381
499
|
else {
|
|
382
500
|
yield fetchAccountBalances(true);
|
|
@@ -400,7 +518,6 @@ const ActiveWalletBalance = ({ isLoading = false, }) => {
|
|
|
400
518
|
fetchAccountBalances,
|
|
401
519
|
refetchShielded,
|
|
402
520
|
supportsShielded,
|
|
403
|
-
activeShieldTab,
|
|
404
521
|
]);
|
|
405
522
|
const primaryWalletNativeBalance = () => {
|
|
406
523
|
if (!primaryWallet ||
|