@dynamic-labs/sdk-react-core 4.83.1 → 4.83.2-alpha.0
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 -0
- package/package.cjs +4 -4
- package/package.js +4 -4
- package/package.json +15 -15
- package/src/lib/client/extension/deprecated/mfa/verifyTotpMfaDevice/verifyTotpMfaDevice.d.ts +1 -1
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.cjs +2 -0
- package/src/lib/components/SendBalancePageLayout/SendBalancePageLayout.js +2 -0
- package/src/lib/components/SendBalancePageLayout/components/TokensBalanceDropdown/TokensBalanceDropdown.cjs +1 -0
- package/src/lib/components/SendBalancePageLayout/components/TokensBalanceDropdown/TokensBalanceDropdown.js +1 -0
- package/src/lib/data/api/aleo/getAleoCuratedPrices.d.ts +9 -0
- package/src/lib/styles/index.shadow.cjs +1 -1
- package/src/lib/styles/index.shadow.js +1 -1
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/buildTokenKey.cjs +18 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/buildTokenKey.d.ts +18 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/buildTokenKey.js +14 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/index.d.ts +2 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/nativeTokenKey.cjs +15 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/nativeTokenKey.d.ts +8 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/nativeTokenKey.js +11 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.cjs +50 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.d.ts +12 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/pollOnShielded.js +45 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs +48 -41
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts +11 -0
- package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js +47 -40
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.cjs +43 -29
- package/src/lib/utils/hooks/useAleoShieldedBalances/useAleoShieldedBalances.js +43 -29
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.cjs +45 -2
- package/src/lib/widgets/DynamicWidget/components/ActiveWalletBalance/ActiveWalletBalance.js +46 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
+
|
|
6
|
+
var nativeTokenKey = require('./nativeTokenKey.cjs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Per-token idempotency key used by `useAleoAutoShieldSponsoredTokens`
|
|
10
|
+
* to dedup auto-shield candidates and by consumers (e.g.
|
|
11
|
+
* `ActiveWalletBalance`) to read back which tokens are currently
|
|
12
|
+
* mid-broadcast via `currentlyShieldingTokenKeys`. Returns an empty
|
|
13
|
+
* string when neither an address nor the native flag is set so callers
|
|
14
|
+
* can treat the token as "no usable key" and skip it.
|
|
15
|
+
*/
|
|
16
|
+
const buildTokenKey = (token) => { var _a; return (_a = token.address) !== null && _a !== void 0 ? _a : (token.isNative ? nativeTokenKey.nativeTokenKey : ''); };
|
|
17
|
+
|
|
18
|
+
exports.buildTokenKey = buildTokenKey;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument shape — narrowed to just what the key derivation needs so the
|
|
3
|
+
* helper can be called with a partial `TokenBalance` or with a raw
|
|
4
|
+
* `{ address, isNative }` pair from any consumer.
|
|
5
|
+
*/
|
|
6
|
+
export type BuildTokenKeyArgs = {
|
|
7
|
+
address?: string;
|
|
8
|
+
isNative?: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Per-token idempotency key used by `useAleoAutoShieldSponsoredTokens`
|
|
12
|
+
* to dedup auto-shield candidates and by consumers (e.g.
|
|
13
|
+
* `ActiveWalletBalance`) to read back which tokens are currently
|
|
14
|
+
* mid-broadcast via `currentlyShieldingTokenKeys`. Returns an empty
|
|
15
|
+
* string when neither an address nor the native flag is set so callers
|
|
16
|
+
* can treat the token as "no usable key" and skip it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const buildTokenKey: (token: BuildTokenKeyArgs) => string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { nativeTokenKey } from './nativeTokenKey.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-token idempotency key used by `useAleoAutoShieldSponsoredTokens`
|
|
6
|
+
* to dedup auto-shield candidates and by consumers (e.g.
|
|
7
|
+
* `ActiveWalletBalance`) to read back which tokens are currently
|
|
8
|
+
* mid-broadcast via `currentlyShieldingTokenKeys`. Returns an empty
|
|
9
|
+
* string when neither an address nor the native flag is set so callers
|
|
10
|
+
* can treat the token as "no usable key" and skip it.
|
|
11
|
+
*/
|
|
12
|
+
const buildTokenKey = (token) => { var _a; return (_a = token.address) !== null && _a !== void 0 ? _a : (token.isNative ? nativeTokenKey : ''); };
|
|
13
|
+
|
|
14
|
+
export { buildTokenKey };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stand-in key for `credits.aleo` — the Aleo native asset has no contract
|
|
8
|
+
* address from redcoast's perspective, so anywhere we'd otherwise reach
|
|
9
|
+
* for `token.address` as the idempotency / dedup key we use this
|
|
10
|
+
* sentinel instead. Centralised so the hook and any consumer (e.g. the
|
|
11
|
+
* `Shield Manually` CTA in `ActiveWalletBalance`) read the same value.
|
|
12
|
+
*/
|
|
13
|
+
const nativeTokenKey = '__native__';
|
|
14
|
+
|
|
15
|
+
exports.nativeTokenKey = nativeTokenKey;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in key for `credits.aleo` — the Aleo native asset has no contract
|
|
3
|
+
* address from redcoast's perspective, so anywhere we'd otherwise reach
|
|
4
|
+
* for `token.address` as the idempotency / dedup key we use this
|
|
5
|
+
* sentinel instead. Centralised so the hook and any consumer (e.g. the
|
|
6
|
+
* `Shield Manually` CTA in `ActiveWalletBalance`) read the same value.
|
|
7
|
+
*/
|
|
8
|
+
export declare const nativeTokenKey = "__native__";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
/**
|
|
3
|
+
* Stand-in key for `credits.aleo` — the Aleo native asset has no contract
|
|
4
|
+
* address from redcoast's perspective, so anywhere we'd otherwise reach
|
|
5
|
+
* for `token.address` as the idempotency / dedup key we use this
|
|
6
|
+
* sentinel instead. Centralised so the hook and any consumer (e.g. the
|
|
7
|
+
* `Shield Manually` CTA in `ActiveWalletBalance`) read the same value.
|
|
8
|
+
*/
|
|
9
|
+
const nativeTokenKey = '__native__';
|
|
10
|
+
|
|
11
|
+
export { nativeTokenKey };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
5
|
+
|
|
6
|
+
var _tslib = require('../../../../../_virtual/_tslib.cjs');
|
|
7
|
+
|
|
8
|
+
// Backoff schedule for the post-shield refresh. `shieldToken` resolves
|
|
9
|
+
// when the relay accepts the broadcast, not when the public→private
|
|
10
|
+
// transition confirms on-chain. Aleo block time + RecordScanner indexer
|
|
11
|
+
// lag means the immediate post-broadcast fetch usually returns the
|
|
12
|
+
// pre-confirmation balance, so we re-poll for ~45s before giving up.
|
|
13
|
+
//
|
|
14
|
+
// Shared between the auto-shield hook (background) and the widget's
|
|
15
|
+
// manual Shield Manually CTA (foreground), so both flows update the
|
|
16
|
+
// unshielded and shielded balances on the same cadence — previously
|
|
17
|
+
// the manual flow refreshed exactly once and the unshielded row
|
|
18
|
+
// stayed at its pre-broadcast value for the full RecordScanner lag,
|
|
19
|
+
// making a successful shield look like a no-op.
|
|
20
|
+
const REFRESH_DELAYS_MS = [
|
|
21
|
+
3000, 5000, 8000, 12000, 18000,
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* After a successful shield broadcast, poll `onShielded` on a backoff
|
|
25
|
+
* schedule so the consumer sees both the shielded and unshielded balances
|
|
26
|
+
* update without a manual refresh click. Stops early on cancel; swallows
|
|
27
|
+
* per-iteration errors (refresh is best-effort).
|
|
28
|
+
*
|
|
29
|
+
* `isCancelled` is a thunk so callers can hook it up to whatever liveness
|
|
30
|
+
* signal they have (effect cleanup `cancelled` flag, abort signal, mount
|
|
31
|
+
* ref, etc.) without this module having to know about React lifecycle.
|
|
32
|
+
*/
|
|
33
|
+
const pollOnShielded = (onShielded, isCancelled) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
for (const delay of REFRESH_DELAYS_MS) {
|
|
35
|
+
if (isCancelled())
|
|
36
|
+
return;
|
|
37
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
38
|
+
if (isCancelled())
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
yield onShielded();
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
/* swallow — refresh is best-effort */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
exports.REFRESH_DELAYS_MS = REFRESH_DELAYS_MS;
|
|
50
|
+
exports.pollOnShielded = pollOnShielded;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const REFRESH_DELAYS_MS: ReadonlyArray<number>;
|
|
2
|
+
/**
|
|
3
|
+
* After a successful shield broadcast, poll `onShielded` on a backoff
|
|
4
|
+
* schedule so the consumer sees both the shielded and unshielded balances
|
|
5
|
+
* update without a manual refresh click. Stops early on cancel; swallows
|
|
6
|
+
* per-iteration errors (refresh is best-effort).
|
|
7
|
+
*
|
|
8
|
+
* `isCancelled` is a thunk so callers can hook it up to whatever liveness
|
|
9
|
+
* signal they have (effect cleanup `cancelled` flag, abort signal, mount
|
|
10
|
+
* ref, etc.) without this module having to know about React lifecycle.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pollOnShielded: (onShielded: () => Promise<void> | void, isCancelled: () => boolean) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { __awaiter } from '../../../../../_virtual/_tslib.js';
|
|
3
|
+
|
|
4
|
+
// Backoff schedule for the post-shield refresh. `shieldToken` resolves
|
|
5
|
+
// when the relay accepts the broadcast, not when the public→private
|
|
6
|
+
// transition confirms on-chain. Aleo block time + RecordScanner indexer
|
|
7
|
+
// lag means the immediate post-broadcast fetch usually returns the
|
|
8
|
+
// pre-confirmation balance, so we re-poll for ~45s before giving up.
|
|
9
|
+
//
|
|
10
|
+
// Shared between the auto-shield hook (background) and the widget's
|
|
11
|
+
// manual Shield Manually CTA (foreground), so both flows update the
|
|
12
|
+
// unshielded and shielded balances on the same cadence — previously
|
|
13
|
+
// the manual flow refreshed exactly once and the unshielded row
|
|
14
|
+
// stayed at its pre-broadcast value for the full RecordScanner lag,
|
|
15
|
+
// making a successful shield look like a no-op.
|
|
16
|
+
const REFRESH_DELAYS_MS = [
|
|
17
|
+
3000, 5000, 8000, 12000, 18000,
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* After a successful shield broadcast, poll `onShielded` on a backoff
|
|
21
|
+
* schedule so the consumer sees both the shielded and unshielded balances
|
|
22
|
+
* update without a manual refresh click. Stops early on cancel; swallows
|
|
23
|
+
* per-iteration errors (refresh is best-effort).
|
|
24
|
+
*
|
|
25
|
+
* `isCancelled` is a thunk so callers can hook it up to whatever liveness
|
|
26
|
+
* signal they have (effect cleanup `cancelled` flag, abort signal, mount
|
|
27
|
+
* ref, etc.) without this module having to know about React lifecycle.
|
|
28
|
+
*/
|
|
29
|
+
const pollOnShielded = (onShielded, isCancelled) => __awaiter(void 0, void 0, void 0, function* () {
|
|
30
|
+
for (const delay of REFRESH_DELAYS_MS) {
|
|
31
|
+
if (isCancelled())
|
|
32
|
+
return;
|
|
33
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
34
|
+
if (isCancelled())
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
yield onShielded();
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
/* swallow — refresh is best-effort */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export { REFRESH_DELAYS_MS, pollOnShielded };
|
package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.cjs
CHANGED
|
@@ -5,15 +5,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
|
|
6
6
|
var _tslib = require('../../../../../_virtual/_tslib.cjs');
|
|
7
7
|
var React = require('react');
|
|
8
|
+
var buildTokenKey = require('./buildTokenKey.cjs');
|
|
9
|
+
var nativeTokenKey = require('./nativeTokenKey.cjs');
|
|
10
|
+
var pollOnShielded = require('./pollOnShielded.cjs');
|
|
8
11
|
|
|
9
|
-
// Backoff schedule for the post-shield refresh. `shieldToken` resolves
|
|
10
|
-
// when the relay accepts the broadcast, not when the public→private
|
|
11
|
-
// transition confirms on-chain. Aleo block time + RecordScanner indexer
|
|
12
|
-
// lag means the immediate post-broadcast fetch usually returns the
|
|
13
|
-
// pre-confirmation balance, so we re-poll for ~45s before giving up.
|
|
14
|
-
const REFRESH_DELAYS_MS = [
|
|
15
|
-
3000, 5000, 8000, 12000, 18000,
|
|
16
|
-
];
|
|
17
12
|
/**
|
|
18
13
|
* Selects which of `tokens` should attempt an auto-shield on this cycle.
|
|
19
14
|
* A token is a candidate when:
|
|
@@ -29,7 +24,6 @@ const REFRESH_DELAYS_MS = [
|
|
|
29
24
|
const buildShieldCandidates = (args) => {
|
|
30
25
|
const { tokens, accountAddress, shieldHandle, seenKeys } = args;
|
|
31
26
|
return tokens.filter((token) => {
|
|
32
|
-
var _a;
|
|
33
27
|
if (!token.rawBalance || token.rawBalance <= 0)
|
|
34
28
|
return false;
|
|
35
29
|
if (!shieldHandle.canShieldToken({
|
|
@@ -38,7 +32,7 @@ const buildShieldCandidates = (args) => {
|
|
|
38
32
|
})) {
|
|
39
33
|
return false;
|
|
40
34
|
}
|
|
41
|
-
const tokenKey =
|
|
35
|
+
const tokenKey = buildTokenKey.buildTokenKey(token);
|
|
42
36
|
if (!tokenKey)
|
|
43
37
|
return false;
|
|
44
38
|
return !seenKeys.has(`${accountAddress}:${tokenKey}`);
|
|
@@ -52,20 +46,20 @@ const buildShieldCandidates = (args) => {
|
|
|
52
46
|
* so the effect-cleanup `cancelled` flag stays in scope.
|
|
53
47
|
*/
|
|
54
48
|
const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
-
var _a, _b, _c
|
|
56
|
-
const { accountAddress, shieldHandle, seenKeys, isCancelled } = deps;
|
|
57
|
-
const tokenKey = (
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, } = deps;
|
|
51
|
+
const tokenKey = buildTokenKey.buildTokenKey(token) || nativeTokenKey.nativeTokenKey;
|
|
58
52
|
const key = `${accountAddress}:${tokenKey}`;
|
|
59
53
|
seenKeys.add(key);
|
|
60
54
|
let sponsored = false;
|
|
61
55
|
try {
|
|
62
56
|
sponsored =
|
|
63
|
-
(
|
|
57
|
+
(_b = (yield ((_a = shieldHandle.isShieldSponsored) === null || _a === void 0 ? void 0 : _a.call(shieldHandle, {
|
|
64
58
|
address: token.address,
|
|
65
59
|
isNative: token.isNative,
|
|
66
|
-
})))) !== null &&
|
|
60
|
+
})))) !== null && _b !== void 0 ? _b : false;
|
|
67
61
|
}
|
|
68
|
-
catch (
|
|
62
|
+
catch (_d) {
|
|
69
63
|
sponsored = false;
|
|
70
64
|
}
|
|
71
65
|
if (isCancelled())
|
|
@@ -74,11 +68,12 @@ const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void
|
|
|
74
68
|
seenKeys.delete(key);
|
|
75
69
|
return false;
|
|
76
70
|
}
|
|
77
|
-
const atomic = BigInt(Math.round((
|
|
71
|
+
const atomic = BigInt(Math.round((_c = token.rawBalance) !== null && _c !== void 0 ? _c : 0));
|
|
78
72
|
if (atomic <= BigInt(0)) {
|
|
79
73
|
seenKeys.delete(key);
|
|
80
74
|
return false;
|
|
81
75
|
}
|
|
76
|
+
inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.add(tokenKey);
|
|
82
77
|
try {
|
|
83
78
|
yield shieldHandle.shieldToken({
|
|
84
79
|
amount: atomic,
|
|
@@ -87,30 +82,12 @@ const tryShieldOneToken = (token, deps) => _tslib.__awaiter(void 0, void 0, void
|
|
|
87
82
|
});
|
|
88
83
|
return true;
|
|
89
84
|
}
|
|
90
|
-
catch (
|
|
85
|
+
catch (_e) {
|
|
91
86
|
seenKeys.delete(key);
|
|
92
87
|
return false;
|
|
93
88
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* After a successful shield broadcast, poll `onShielded` on a backoff
|
|
97
|
-
* schedule so the consumer sees both the shielded and unshielded balances
|
|
98
|
-
* update without a manual refresh click. Stops early on cancel; swallows
|
|
99
|
-
* per-iteration errors (refresh is best-effort).
|
|
100
|
-
*/
|
|
101
|
-
const pollOnShielded = (onShielded, isCancelled) => _tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
-
for (const delay of REFRESH_DELAYS_MS) {
|
|
103
|
-
if (isCancelled())
|
|
104
|
-
return;
|
|
105
|
-
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
106
|
-
if (isCancelled())
|
|
107
|
-
return;
|
|
108
|
-
try {
|
|
109
|
-
yield onShielded();
|
|
110
|
-
}
|
|
111
|
-
catch (_g) {
|
|
112
|
-
/* swallow — refresh is best-effort */
|
|
113
|
-
}
|
|
89
|
+
finally {
|
|
90
|
+
inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.delete(tokenKey);
|
|
114
91
|
}
|
|
115
92
|
});
|
|
116
93
|
const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }) => {
|
|
@@ -148,6 +125,35 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
148
125
|
mountedRef.current = false;
|
|
149
126
|
};
|
|
150
127
|
}, []);
|
|
128
|
+
// Identity-stable set of token keys we're currently broadcasting for.
|
|
129
|
+
// Mutated in `tryShieldOneToken` (add on entry, delete in `finally`)
|
|
130
|
+
// via the `inFlightKeysAdapter` below. We rebuild the returned `Set`
|
|
131
|
+
// as a new instance on every mutation so React consumers re-render;
|
|
132
|
+
// the underlying ref-held Set is internal and never exposed.
|
|
133
|
+
const inFlightKeysRef = React.useRef(new Set());
|
|
134
|
+
const [currentlyShieldingTokenKeys, setCurrentlyShieldingTokenKeys] = React.useState(() => new Set());
|
|
135
|
+
// Stable adapter we hand to `tryShieldOneToken`. Each add/delete
|
|
136
|
+
// mutates the ref-held set and publishes a fresh ReadonlySet snapshot
|
|
137
|
+
// to state so subscribers re-render. We can't pass `inFlightKeysRef.current`
|
|
138
|
+
// directly because the consumer can't subscribe to mutation — the
|
|
139
|
+
// state copy is what makes this observable from React.
|
|
140
|
+
const inFlightKeysAdapter = React.useMemo(() => ({
|
|
141
|
+
add: (key) => {
|
|
142
|
+
inFlightKeysRef.current.add(key);
|
|
143
|
+
if (mountedRef.current) {
|
|
144
|
+
setCurrentlyShieldingTokenKeys(new Set(inFlightKeysRef.current));
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
delete: (key) => {
|
|
148
|
+
inFlightKeysRef.current.delete(key);
|
|
149
|
+
if (mountedRef.current) {
|
|
150
|
+
setCurrentlyShieldingTokenKeys(new Set(inFlightKeysRef.current));
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
154
|
+
// mountedRef is stable; we read its `.current` inside the closures
|
|
155
|
+
// to avoid resubscribing consumers on every mount.
|
|
156
|
+
[]);
|
|
151
157
|
// Content fingerprint for the balance list. We re-run the effect only
|
|
152
158
|
// when balances *meaningfully* change (token added/removed/balance
|
|
153
159
|
// shifted), not on every render where the parent passes a new array
|
|
@@ -158,7 +164,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
158
164
|
const balanceFingerprint = React.useMemo(() => (unshieldedTokenBalances !== null && unshieldedTokenBalances !== void 0 ? unshieldedTokenBalances : [])
|
|
159
165
|
.map((t) => {
|
|
160
166
|
var _a, _b;
|
|
161
|
-
return `${(_a = t.address) !== null && _a !== void 0 ? _a :
|
|
167
|
+
return `${(_a = t.address) !== null && _a !== void 0 ? _a : nativeTokenKey.nativeTokenKey}|${t.isNative ? 1 : 0}|${(_b = t.rawBalance) !== null && _b !== void 0 ? _b : 0}`;
|
|
162
168
|
})
|
|
163
169
|
// Use `localeCompare` so the sort is locale-aware and stable across
|
|
164
170
|
// JS engines (default `Array.prototype.sort` on strings is
|
|
@@ -210,6 +216,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
210
216
|
return;
|
|
211
217
|
const ok = yield tryShieldOneToken(token, {
|
|
212
218
|
accountAddress,
|
|
219
|
+
inFlightTokenKeys: inFlightKeysAdapter,
|
|
213
220
|
isCancelled,
|
|
214
221
|
seenKeys: seenKeysRef.current,
|
|
215
222
|
shieldHandle,
|
|
@@ -218,7 +225,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
218
225
|
didShield = true;
|
|
219
226
|
}
|
|
220
227
|
if (didShield && !cancelled && onShielded) {
|
|
221
|
-
yield pollOnShielded(onShielded, isCancelled);
|
|
228
|
+
yield pollOnShielded.pollOnShielded(onShielded, isCancelled);
|
|
222
229
|
}
|
|
223
230
|
});
|
|
224
231
|
run()
|
|
@@ -257,7 +264,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
257
264
|
// cancel an in-flight shield ceremony.
|
|
258
265
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
259
266
|
}, [accountAddress, balanceFingerprint, shieldHandle, onShielded]);
|
|
260
|
-
return { isShielding };
|
|
267
|
+
return { currentlyShieldingTokenKeys, isShielding };
|
|
261
268
|
};
|
|
262
269
|
|
|
263
270
|
exports.useAleoAutoShieldSponsoredTokens = useAleoAutoShieldSponsoredTokens;
|
package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.d.ts
CHANGED
|
@@ -54,6 +54,17 @@ export type UseAleoAutoShieldSponsoredTokensResult = {
|
|
|
54
54
|
* Goes back to `false` on cleanup or once the run resolves.
|
|
55
55
|
*/
|
|
56
56
|
isShielding: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Token keys (`token.address` or `nativeTokenKey`) for which a shield
|
|
59
|
+
* ceremony is currently in flight from this hook. Stable identity
|
|
60
|
+
* across renders — only changes when a ceremony starts or ends, not
|
|
61
|
+
* when other parts of the hook state churn. Consumers (e.g. the
|
|
62
|
+
* widget's Shield Manually CTA) read this set to suppress duplicate
|
|
63
|
+
* CTAs for tokens auto-shield is already broadcasting; the connector
|
|
64
|
+
* has its own dedup, but hiding the CTA also prevents the user from
|
|
65
|
+
* seeing a stale spinner snap to a second one.
|
|
66
|
+
*/
|
|
67
|
+
currentlyShieldingTokenKeys: ReadonlySet<string>;
|
|
57
68
|
};
|
|
58
69
|
export declare const useAleoAutoShieldSponsoredTokens: ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }: UseAleoAutoShieldSponsoredTokensArgs) => UseAleoAutoShieldSponsoredTokensResult;
|
|
59
70
|
export {};
|
package/src/lib/utils/hooks/useAleoAutoShieldSponsoredTokens/useAleoAutoShieldSponsoredTokens.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { __awaiter } from '../../../../../_virtual/_tslib.js';
|
|
3
3
|
import { useRef, useState, useEffect, useMemo } from 'react';
|
|
4
|
+
import { buildTokenKey } from './buildTokenKey.js';
|
|
5
|
+
import { nativeTokenKey } from './nativeTokenKey.js';
|
|
6
|
+
import { pollOnShielded } from './pollOnShielded.js';
|
|
4
7
|
|
|
5
|
-
// Backoff schedule for the post-shield refresh. `shieldToken` resolves
|
|
6
|
-
// when the relay accepts the broadcast, not when the public→private
|
|
7
|
-
// transition confirms on-chain. Aleo block time + RecordScanner indexer
|
|
8
|
-
// lag means the immediate post-broadcast fetch usually returns the
|
|
9
|
-
// pre-confirmation balance, so we re-poll for ~45s before giving up.
|
|
10
|
-
const REFRESH_DELAYS_MS = [
|
|
11
|
-
3000, 5000, 8000, 12000, 18000,
|
|
12
|
-
];
|
|
13
8
|
/**
|
|
14
9
|
* Selects which of `tokens` should attempt an auto-shield on this cycle.
|
|
15
10
|
* A token is a candidate when:
|
|
@@ -25,7 +20,6 @@ const REFRESH_DELAYS_MS = [
|
|
|
25
20
|
const buildShieldCandidates = (args) => {
|
|
26
21
|
const { tokens, accountAddress, shieldHandle, seenKeys } = args;
|
|
27
22
|
return tokens.filter((token) => {
|
|
28
|
-
var _a;
|
|
29
23
|
if (!token.rawBalance || token.rawBalance <= 0)
|
|
30
24
|
return false;
|
|
31
25
|
if (!shieldHandle.canShieldToken({
|
|
@@ -34,7 +28,7 @@ const buildShieldCandidates = (args) => {
|
|
|
34
28
|
})) {
|
|
35
29
|
return false;
|
|
36
30
|
}
|
|
37
|
-
const tokenKey = (
|
|
31
|
+
const tokenKey = buildTokenKey(token);
|
|
38
32
|
if (!tokenKey)
|
|
39
33
|
return false;
|
|
40
34
|
return !seenKeys.has(`${accountAddress}:${tokenKey}`);
|
|
@@ -48,20 +42,20 @@ const buildShieldCandidates = (args) => {
|
|
|
48
42
|
* so the effect-cleanup `cancelled` flag stays in scope.
|
|
49
43
|
*/
|
|
50
44
|
const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
-
var _a, _b, _c
|
|
52
|
-
const { accountAddress, shieldHandle, seenKeys, isCancelled } = deps;
|
|
53
|
-
const tokenKey = (
|
|
45
|
+
var _a, _b, _c;
|
|
46
|
+
const { accountAddress, shieldHandle, seenKeys, isCancelled, inFlightTokenKeys, } = deps;
|
|
47
|
+
const tokenKey = buildTokenKey(token) || nativeTokenKey;
|
|
54
48
|
const key = `${accountAddress}:${tokenKey}`;
|
|
55
49
|
seenKeys.add(key);
|
|
56
50
|
let sponsored = false;
|
|
57
51
|
try {
|
|
58
52
|
sponsored =
|
|
59
|
-
(
|
|
53
|
+
(_b = (yield ((_a = shieldHandle.isShieldSponsored) === null || _a === void 0 ? void 0 : _a.call(shieldHandle, {
|
|
60
54
|
address: token.address,
|
|
61
55
|
isNative: token.isNative,
|
|
62
|
-
})))) !== null &&
|
|
56
|
+
})))) !== null && _b !== void 0 ? _b : false;
|
|
63
57
|
}
|
|
64
|
-
catch (
|
|
58
|
+
catch (_d) {
|
|
65
59
|
sponsored = false;
|
|
66
60
|
}
|
|
67
61
|
if (isCancelled())
|
|
@@ -70,11 +64,12 @@ const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, fun
|
|
|
70
64
|
seenKeys.delete(key);
|
|
71
65
|
return false;
|
|
72
66
|
}
|
|
73
|
-
const atomic = BigInt(Math.round((
|
|
67
|
+
const atomic = BigInt(Math.round((_c = token.rawBalance) !== null && _c !== void 0 ? _c : 0));
|
|
74
68
|
if (atomic <= BigInt(0)) {
|
|
75
69
|
seenKeys.delete(key);
|
|
76
70
|
return false;
|
|
77
71
|
}
|
|
72
|
+
inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.add(tokenKey);
|
|
78
73
|
try {
|
|
79
74
|
yield shieldHandle.shieldToken({
|
|
80
75
|
amount: atomic,
|
|
@@ -83,30 +78,12 @@ const tryShieldOneToken = (token, deps) => __awaiter(void 0, void 0, void 0, fun
|
|
|
83
78
|
});
|
|
84
79
|
return true;
|
|
85
80
|
}
|
|
86
|
-
catch (
|
|
81
|
+
catch (_e) {
|
|
87
82
|
seenKeys.delete(key);
|
|
88
83
|
return false;
|
|
89
84
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* After a successful shield broadcast, poll `onShielded` on a backoff
|
|
93
|
-
* schedule so the consumer sees both the shielded and unshielded balances
|
|
94
|
-
* update without a manual refresh click. Stops early on cancel; swallows
|
|
95
|
-
* per-iteration errors (refresh is best-effort).
|
|
96
|
-
*/
|
|
97
|
-
const pollOnShielded = (onShielded, isCancelled) => __awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
-
for (const delay of REFRESH_DELAYS_MS) {
|
|
99
|
-
if (isCancelled())
|
|
100
|
-
return;
|
|
101
|
-
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
102
|
-
if (isCancelled())
|
|
103
|
-
return;
|
|
104
|
-
try {
|
|
105
|
-
yield onShielded();
|
|
106
|
-
}
|
|
107
|
-
catch (_g) {
|
|
108
|
-
/* swallow — refresh is best-effort */
|
|
109
|
-
}
|
|
85
|
+
finally {
|
|
86
|
+
inFlightTokenKeys === null || inFlightTokenKeys === void 0 ? void 0 : inFlightTokenKeys.delete(tokenKey);
|
|
110
87
|
}
|
|
111
88
|
});
|
|
112
89
|
const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalances, shieldHandle, onShielded, }) => {
|
|
@@ -144,6 +121,35 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
144
121
|
mountedRef.current = false;
|
|
145
122
|
};
|
|
146
123
|
}, []);
|
|
124
|
+
// Identity-stable set of token keys we're currently broadcasting for.
|
|
125
|
+
// Mutated in `tryShieldOneToken` (add on entry, delete in `finally`)
|
|
126
|
+
// via the `inFlightKeysAdapter` below. We rebuild the returned `Set`
|
|
127
|
+
// as a new instance on every mutation so React consumers re-render;
|
|
128
|
+
// the underlying ref-held Set is internal and never exposed.
|
|
129
|
+
const inFlightKeysRef = useRef(new Set());
|
|
130
|
+
const [currentlyShieldingTokenKeys, setCurrentlyShieldingTokenKeys] = useState(() => new Set());
|
|
131
|
+
// Stable adapter we hand to `tryShieldOneToken`. Each add/delete
|
|
132
|
+
// mutates the ref-held set and publishes a fresh ReadonlySet snapshot
|
|
133
|
+
// to state so subscribers re-render. We can't pass `inFlightKeysRef.current`
|
|
134
|
+
// directly because the consumer can't subscribe to mutation — the
|
|
135
|
+
// state copy is what makes this observable from React.
|
|
136
|
+
const inFlightKeysAdapter = useMemo(() => ({
|
|
137
|
+
add: (key) => {
|
|
138
|
+
inFlightKeysRef.current.add(key);
|
|
139
|
+
if (mountedRef.current) {
|
|
140
|
+
setCurrentlyShieldingTokenKeys(new Set(inFlightKeysRef.current));
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
delete: (key) => {
|
|
144
|
+
inFlightKeysRef.current.delete(key);
|
|
145
|
+
if (mountedRef.current) {
|
|
146
|
+
setCurrentlyShieldingTokenKeys(new Set(inFlightKeysRef.current));
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
// mountedRef is stable; we read its `.current` inside the closures
|
|
151
|
+
// to avoid resubscribing consumers on every mount.
|
|
152
|
+
[]);
|
|
147
153
|
// Content fingerprint for the balance list. We re-run the effect only
|
|
148
154
|
// when balances *meaningfully* change (token added/removed/balance
|
|
149
155
|
// shifted), not on every render where the parent passes a new array
|
|
@@ -154,7 +160,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
154
160
|
const balanceFingerprint = useMemo(() => (unshieldedTokenBalances !== null && unshieldedTokenBalances !== void 0 ? unshieldedTokenBalances : [])
|
|
155
161
|
.map((t) => {
|
|
156
162
|
var _a, _b;
|
|
157
|
-
return `${(_a = t.address) !== null && _a !== void 0 ? _a :
|
|
163
|
+
return `${(_a = t.address) !== null && _a !== void 0 ? _a : nativeTokenKey}|${t.isNative ? 1 : 0}|${(_b = t.rawBalance) !== null && _b !== void 0 ? _b : 0}`;
|
|
158
164
|
})
|
|
159
165
|
// Use `localeCompare` so the sort is locale-aware and stable across
|
|
160
166
|
// JS engines (default `Array.prototype.sort` on strings is
|
|
@@ -206,6 +212,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
206
212
|
return;
|
|
207
213
|
const ok = yield tryShieldOneToken(token, {
|
|
208
214
|
accountAddress,
|
|
215
|
+
inFlightTokenKeys: inFlightKeysAdapter,
|
|
209
216
|
isCancelled,
|
|
210
217
|
seenKeys: seenKeysRef.current,
|
|
211
218
|
shieldHandle,
|
|
@@ -253,7 +260,7 @@ const useAleoAutoShieldSponsoredTokens = ({ accountAddress, unshieldedTokenBalan
|
|
|
253
260
|
// cancel an in-flight shield ceremony.
|
|
254
261
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
255
262
|
}, [accountAddress, balanceFingerprint, shieldHandle, onShielded]);
|
|
256
|
-
return { isShielding };
|
|
263
|
+
return { currentlyShieldingTokenKeys, isShielding };
|
|
257
264
|
};
|
|
258
265
|
|
|
259
266
|
export { useAleoAutoShieldSponsoredTokens };
|