@openfort/react 1.1.3 → 1.2.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.
Files changed (154) hide show
  1. package/build/assets/icons.js +1 -1
  2. package/build/assets/logos.d.ts +12 -0
  3. package/build/assets/logos.js +7 -0
  4. package/build/assets/logos.js.map +1 -1
  5. package/build/components/Common/CopyToClipboard/CopyIconButton.d.ts +3 -1
  6. package/build/components/Common/CopyToClipboard/CopyIconButton.js +4 -4
  7. package/build/components/Common/CustomQRCode/index.d.ts +1 -1
  8. package/build/components/Common/CustomQRCode/index.js +3 -3
  9. package/build/components/Common/CustomQRCode/styles.d.ts +1 -0
  10. package/build/components/Common/CustomQRCode/styles.js +4 -4
  11. package/build/components/Common/CustomQRCode/types.d.ts +2 -0
  12. package/build/components/Common/Modal/styles.js +4 -1
  13. package/build/components/Common/Modal/styles.js.map +1 -1
  14. package/build/components/ConnectModal/index.js +14 -2
  15. package/build/components/ConnectModal/index.js.map +1 -1
  16. package/build/components/Openfort/OpenfortProvider.js +4 -1
  17. package/build/components/Openfort/OpenfortProvider.js.map +1 -1
  18. package/build/components/Openfort/types.d.ts +86 -0
  19. package/build/components/Openfort/types.js +22 -1
  20. package/build/components/Openfort/types.js.map +1 -1
  21. package/build/components/Pages/AssetInventory/SolanaAssetInventory.d.ts +6 -0
  22. package/build/components/Pages/AssetInventory/SolanaAssetInventory.js +42 -0
  23. package/build/components/Pages/AssetInventory/SolanaAssetInventory.js.map +1 -0
  24. package/build/components/Pages/Buy/index.js +3 -2
  25. package/build/components/Pages/Buy/index.js.map +1 -1
  26. package/build/components/Pages/BuySelectProvider/index.js +1 -1
  27. package/build/components/Pages/Connected/EthereumConnected.js +8 -32
  28. package/build/components/Pages/Connected/EthereumConnected.js.map +1 -1
  29. package/build/components/Pages/Connected/SolanaConnected.js +9 -4
  30. package/build/components/Pages/Connected/SolanaConnected.js.map +1 -1
  31. package/build/components/Pages/Deposit/AddressPageLink.d.ts +7 -0
  32. package/build/components/Pages/Deposit/AddressPageLink.js +17 -0
  33. package/build/components/Pages/Deposit/AddressPageLink.js.map +1 -0
  34. package/build/components/Pages/Deposit/AssetChainLogo.d.ts +9 -0
  35. package/build/components/Pages/Deposit/AssetChainLogo.js +24 -0
  36. package/build/components/Pages/Deposit/AssetChainLogo.js.map +1 -0
  37. package/build/components/Pages/Deposit/DepositAddressBlock.d.ts +21 -0
  38. package/build/components/Pages/Deposit/DepositAddressBlock.js +28 -0
  39. package/build/components/Pages/Deposit/DepositAddressBlock.js.map +1 -0
  40. package/build/components/Pages/Deposit/DepositProgress.d.ts +15 -0
  41. package/build/components/Pages/Deposit/DepositProgress.js +110 -0
  42. package/build/components/Pages/Deposit/DepositProgress.js.map +1 -0
  43. package/build/components/Pages/Deposit/DepositStatus.d.ts +9 -0
  44. package/build/components/Pages/Deposit/DepositStatus.js +43 -0
  45. package/build/components/Pages/Deposit/DepositStatus.js.map +1 -0
  46. package/build/components/Pages/Deposit/DepositSuccess.d.ts +6 -0
  47. package/build/components/Pages/Deposit/DepositSuccess.js +24 -0
  48. package/build/components/Pages/Deposit/DepositSuccess.js.map +1 -0
  49. package/build/components/Pages/Deposit/Details.d.ts +12 -0
  50. package/build/components/Pages/Deposit/Details.js +40 -0
  51. package/build/components/Pages/Deposit/Details.js.map +1 -0
  52. package/build/components/Pages/Deposit/LogoSelect.d.ts +12 -0
  53. package/build/components/Pages/Deposit/LogoSelect.js +95 -0
  54. package/build/components/Pages/Deposit/LogoSelect.js.map +1 -0
  55. package/build/components/Pages/Deposit/OrDivider.d.ts +2 -0
  56. package/build/components/Pages/Deposit/OrDivider.js +10 -0
  57. package/build/components/Pages/Deposit/OrDivider.js.map +1 -0
  58. package/build/components/Pages/Deposit/RouteSelectors.d.ts +13 -0
  59. package/build/components/Pages/Deposit/RouteSelectors.js +19 -0
  60. package/build/components/Pages/Deposit/RouteSelectors.js.map +1 -0
  61. package/build/components/Pages/Deposit/cexChains.d.ts +9 -0
  62. package/build/components/Pages/Deposit/cexChains.js +23 -0
  63. package/build/components/Pages/Deposit/cexChains.js.map +1 -0
  64. package/build/components/Pages/Deposit/formStyles.d.ts +24 -0
  65. package/build/components/Pages/Deposit/formStyles.js +83 -0
  66. package/build/components/Pages/Deposit/formStyles.js.map +1 -0
  67. package/build/components/Pages/Deposit/index.d.ts +7 -0
  68. package/build/components/Pages/Deposit/index.js +100 -0
  69. package/build/components/Pages/Deposit/index.js.map +1 -0
  70. package/build/components/Pages/Deposit/paymentOptions.d.ts +49 -0
  71. package/build/components/Pages/Deposit/paymentOptions.js +63 -0
  72. package/build/components/Pages/Deposit/paymentOptions.js.map +1 -0
  73. package/build/components/Pages/Deposit/sources.d.ts +17 -0
  74. package/build/components/Pages/Deposit/sources.js +22 -0
  75. package/build/components/Pages/Deposit/sources.js.map +1 -0
  76. package/build/components/Pages/Deposit/styles.d.ts +25 -0
  77. package/build/components/Pages/Deposit/styles.js +167 -0
  78. package/build/components/Pages/Deposit/styles.js.map +1 -0
  79. package/build/components/Pages/Deposit/useDepositRoute.d.ts +35 -0
  80. package/build/components/Pages/Deposit/useDepositRoute.js +107 -0
  81. package/build/components/Pages/Deposit/useDepositRoute.js.map +1 -0
  82. package/build/components/Pages/Deposit/useFundingTarget.d.ts +13 -0
  83. package/build/components/Pages/Deposit/useFundingTarget.js +27 -0
  84. package/build/components/Pages/Deposit/useFundingTarget.js.map +1 -0
  85. package/build/components/Pages/DepositCex/index.d.ts +11 -0
  86. package/build/components/Pages/DepositCex/index.js +230 -0
  87. package/build/components/Pages/DepositCex/index.js.map +1 -0
  88. package/build/components/Pages/DepositCrypto/index.d.ts +8 -0
  89. package/build/components/Pages/DepositCrypto/index.js +31 -0
  90. package/build/components/Pages/DepositCrypto/index.js.map +1 -0
  91. package/build/components/Pages/DepositWallet/DepositWalletDesktop.d.ts +17 -0
  92. package/build/components/Pages/DepositWallet/DepositWalletDesktop.js +148 -0
  93. package/build/components/Pages/DepositWallet/DepositWalletDesktop.js.map +1 -0
  94. package/build/components/Pages/DepositWallet/index.d.ts +9 -0
  95. package/build/components/Pages/DepositWallet/index.js +102 -0
  96. package/build/components/Pages/DepositWallet/index.js.map +1 -0
  97. package/build/components/Pages/DepositWallet/walletDeeplinks.d.ts +48 -0
  98. package/build/components/Pages/DepositWallet/walletDeeplinks.js +107 -0
  99. package/build/components/Pages/DepositWallet/walletDeeplinks.js.map +1 -0
  100. package/build/components/Pages/ExportKey/index.js +10 -2
  101. package/build/components/Pages/ExportKey/index.js.map +1 -1
  102. package/build/components/Pages/NoAssetsAvailable/index.js +5 -21
  103. package/build/components/Pages/NoAssetsAvailable/index.js.map +1 -1
  104. package/build/components/Pages/SelectToken/styles.js +1 -1
  105. package/build/components/Pages/Send/SolanaSend.d.ts +1 -0
  106. package/build/components/Pages/Send/SolanaSend.js +88 -0
  107. package/build/components/Pages/Send/SolanaSend.js.map +1 -0
  108. package/build/components/Pages/Send/index.d.ts +2 -1
  109. package/build/components/Pages/Send/index.js +0 -1
  110. package/build/components/Pages/Send/index.js.map +1 -1
  111. package/build/components/Pages/SendConfirmation/EstimatedFees.js +5 -3
  112. package/build/components/Pages/SendConfirmation/EstimatedFees.js.map +1 -1
  113. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.d.ts +1 -0
  114. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js +77 -0
  115. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js.map +1 -0
  116. package/build/components/Pages/SendConfirmation/index.js +4 -3
  117. package/build/components/Pages/SendConfirmation/index.js.map +1 -1
  118. package/build/components/Pages/SendConfirmation/styles.d.ts +5 -0
  119. package/build/components/Pages/SendConfirmation/styles.js +39 -1
  120. package/build/components/Pages/SendConfirmation/styles.js.map +1 -1
  121. package/build/constants/logos.js +1 -0
  122. package/build/constants/logos.js.map +1 -1
  123. package/build/ethereum/hooks/useEthereumWalletAssets.js +212 -95
  124. package/build/ethereum/hooks/useEthereumWalletAssets.js.map +1 -1
  125. package/build/hooks/openfort/fundingClient.d.ts +34 -0
  126. package/build/hooks/openfort/fundingClient.js +60 -0
  127. package/build/hooks/openfort/fundingClient.js.map +1 -0
  128. package/build/hooks/openfort/useFunding.d.ts +159 -0
  129. package/build/hooks/openfort/useFunding.js +204 -0
  130. package/build/hooks/openfort/useFunding.js.map +1 -0
  131. package/build/hooks/openfort/useFundingChains.d.ts +49 -0
  132. package/build/hooks/openfort/useFundingChains.js +102 -0
  133. package/build/hooks/openfort/useFundingChains.js.map +1 -0
  134. package/build/hooks/useBalance.js +6 -1
  135. package/build/hooks/useBalance.js.map +1 -1
  136. package/build/index.d.ts +4 -1
  137. package/build/index.js +2 -1
  138. package/build/index.js.map +1 -1
  139. package/build/shared/hooks/useAsyncData.d.ts +11 -0
  140. package/build/shared/hooks/useAsyncData.js +60 -13
  141. package/build/shared/hooks/useAsyncData.js.map +1 -1
  142. package/build/solana/hooks/useSolanaWalletAssets.d.ts +24 -0
  143. package/build/solana/hooks/useSolanaWalletAssets.js +86 -0
  144. package/build/solana/hooks/useSolanaWalletAssets.js.map +1 -0
  145. package/build/solana/transfer.d.ts +32 -0
  146. package/build/solana/transfer.js +125 -0
  147. package/build/solana/transfer.js.map +1 -0
  148. package/build/utils/index.d.ts +2 -1
  149. package/build/utils/index.js +1 -1
  150. package/build/version.d.ts +1 -1
  151. package/build/version.js +1 -1
  152. package/build/wagmi/defaultConnectors.js +5 -1
  153. package/build/wagmi/defaultConnectors.js.map +1 -1
  154. package/package.json +10 -2
@@ -0,0 +1,204 @@
1
+ import { SDKConfiguration } from '@openfort/openfort-js';
2
+ import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
3
+ import { useOpenfort } from '../../components/Openfort/useOpenfort.js';
4
+ import { useOpenfortCore } from '../../openfort/useOpenfort.js';
5
+ import { logger } from '../../utils/logger.js';
6
+ import { createFetchFundingClient } from './fundingClient.js';
7
+
8
+ /** Pay-links aren't exposed by the SDK funding namespace yet (CEX is API-deferred). */
9
+ const sdkPayLinkUnavailable = async () => {
10
+ throw new Error('Exchange pay-links are not available through the SDK yet');
11
+ };
12
+ const TERMINAL = ['succeeded', 'bounced', 'expired'];
13
+ const POLL_MS = 4000;
14
+ /**
15
+ * Poll a session until it reaches a terminal state, pushing each update through
16
+ * `onUpdate`. Shared by `fund` (after a payment method is set) and `track` (the
17
+ * CEX rail, watching a hosted Coinbase deposit settle). `isCurrent` guards a
18
+ * stale loop from clobbering newer state after reset/unmount.
19
+ */
20
+ async function pollUntilTerminal(client, onUpdate, start, isCurrent) {
21
+ var _a, _b, _c;
22
+ let current = start;
23
+ let prev = current.status;
24
+ while (!TERMINAL.includes(current.status)) {
25
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
26
+ if (!isCurrent())
27
+ return current;
28
+ current = await client.sessions.get(current.id, { clientSecret: current.clientSecret });
29
+ if (!isCurrent())
30
+ return current;
31
+ onUpdate(current);
32
+ if (current.status !== prev) {
33
+ logger.log('[funding] status', { sessionId: current.id, prev, status: current.status });
34
+ prev = current.status;
35
+ }
36
+ }
37
+ if (current.status === 'succeeded') {
38
+ logger.log('[funding] terminal: delivered', {
39
+ sessionId: current.id,
40
+ status: current.status,
41
+ txHash: (_c = (_b = (_a = current.metadata) === null || _a === void 0 ? void 0 : _a.txHash) !== null && _b !== void 0 ? _b : current.externalId) !== null && _c !== void 0 ? _c : null,
42
+ });
43
+ }
44
+ else {
45
+ // bounced = source delivered but Relay refunded; expired = nothing arrived in time.
46
+ logger.warn('[funding] terminal: failed', { sessionId: current.id, status: current.status });
47
+ }
48
+ return current;
49
+ }
50
+ /**
51
+ * React surface over the funding session API.
52
+ *
53
+ * @returns Session state plus `fund` (run the deposit flow) and `reset`.
54
+ */
55
+ function useFunding(options) {
56
+ var _a, _b, _c;
57
+ const { uiConfig, publishableKey } = useOpenfort();
58
+ const { client: coreClient } = useOpenfortCore();
59
+ // The CEX rail (DepositCex) is served by the Openfort API; everything else uses
60
+ // the standalone funding service at uiConfig.fundingBaseUrl.
61
+ const baseUrl = (options === null || options === void 0 ? void 0 : options.useBackendUrl)
62
+ ? ((_a = SDKConfiguration.getInstance()) === null || _a === void 0 ? void 0 : _a.backendUrl) || 'https://api.openfort.io'
63
+ : ((_b = uiConfig.fundingBaseUrl) !== null && _b !== void 0 ? _b : '');
64
+ const injected = options === null || options === void 0 ? void 0 : options.client;
65
+ // Resolve the client, in order of preference:
66
+ // 1. an explicitly injected client (tests / custom backends),
67
+ // 2. the SDK's `openfort.funding` namespace once available,
68
+ // 3. the fetch adapter over uiConfig.fundingBaseUrl.
69
+ // The SDK namespace covers sessions; pay-links stay on the backend adapter
70
+ // until the API exposes them (CEX is deferred), so we compose the two.
71
+ const client = useMemo(() => {
72
+ var _a;
73
+ if (injected)
74
+ return injected;
75
+ const sdkFunding = coreClient === null || coreClient === void 0 ? void 0 : coreClient.funding;
76
+ const fetchClient = baseUrl ? createFetchFundingClient(baseUrl, publishableKey) : null;
77
+ if (sdkFunding) {
78
+ return { sessions: sdkFunding.sessions, payLink: (_a = fetchClient === null || fetchClient === void 0 ? void 0 : fetchClient.payLink) !== null && _a !== void 0 ? _a : sdkPayLinkUnavailable };
79
+ }
80
+ return fetchClient;
81
+ }, [injected, coreClient, baseUrl, publishableKey]);
82
+ const isAvailable = client != null;
83
+ const [session, setSession] = useState(null);
84
+ const [error, setError] = useState(null);
85
+ const [loading, setLoading] = useState(false);
86
+ // Generation guard: only the latest fund()/reset() updates state, so
87
+ // re-selecting a source mid-poll can't be clobbered by a stale request.
88
+ const generation = useRef(0);
89
+ // Stop any in-flight poll loop on unmount — without this the loop keeps
90
+ // hitting the network until the session turns terminal (up to its 24h TTL).
91
+ useEffect(() => {
92
+ return () => {
93
+ generation.current += 1;
94
+ logger.log('[funding] unmounted — poll loop stopped');
95
+ };
96
+ }, []);
97
+ const reset = useCallback(() => {
98
+ generation.current += 1;
99
+ logger.log('[funding] reset');
100
+ setSession(null);
101
+ setError(null);
102
+ setLoading(false);
103
+ }, []);
104
+ const fund = useCallback(async (target, paymentMethod) => {
105
+ var _a;
106
+ generation.current += 1;
107
+ const gen = generation.current;
108
+ const isCurrent = () => generation.current === gen;
109
+ setError(null);
110
+ setLoading(true);
111
+ logger.log('[funding] fund() start', {
112
+ chain: target.chain,
113
+ currency: target.currency,
114
+ address: target.address,
115
+ paymentMethod: paymentMethod.type,
116
+ sourceChain: paymentMethod.source.chain,
117
+ amount: paymentMethod.source.amount,
118
+ });
119
+ try {
120
+ if (!client) {
121
+ throw new Error('Funding is not configured (set uiConfig.fundingBaseUrl)');
122
+ }
123
+ const created = await client.sessions.create({ target });
124
+ logger.log('[funding] session created', { sessionId: created.id, status: created.status });
125
+ const current = await client.sessions.setPaymentMethod(created.id, {
126
+ clientSecret: created.clientSecret,
127
+ paymentMethod,
128
+ });
129
+ if (!isCurrent())
130
+ return current;
131
+ setSession(current);
132
+ setLoading(false);
133
+ logger.log('[funding] payment method set', {
134
+ sessionId: current.id,
135
+ status: current.status,
136
+ receiverAddress: (_a = current.paymentMethod) === null || _a === void 0 ? void 0 : _a.receiverAddress,
137
+ });
138
+ // The session status encodes both hops: waiting_payment (awaiting the source
139
+ // deposit, e.g. the Coinbase withdrawal) → processing (deposit arrived on-chain,
140
+ // Relay bridging) → succeeded / bounced (Relay refunded) / expired (no deposit).
141
+ return pollUntilTerminal(client, setSession, current, isCurrent);
142
+ }
143
+ catch (e) {
144
+ const err = e instanceof Error ? e : new Error(String(e));
145
+ if (isCurrent()) {
146
+ setError(err);
147
+ setLoading(false);
148
+ logger.error('[funding] fund() failed', err);
149
+ }
150
+ throw err;
151
+ }
152
+ }, [client]);
153
+ const createSession = useCallback(async (target) => {
154
+ if (!client)
155
+ throw new Error('Funding is not configured (set uiConfig.fundingBaseUrl)');
156
+ const created = await client.sessions.create({ target });
157
+ logger.log('[funding] session created (cex)', { sessionId: created.id, status: created.status });
158
+ return created;
159
+ }, [client]);
160
+ const track = useCallback(async (toTrack) => {
161
+ if (!client)
162
+ throw new Error('Funding is not configured (set uiConfig.fundingBaseUrl)');
163
+ generation.current += 1;
164
+ const gen = generation.current;
165
+ const isCurrent = () => generation.current === gen;
166
+ setError(null);
167
+ try {
168
+ const start = await client.sessions.get(toTrack.id, { clientSecret: toTrack.clientSecret });
169
+ if (!isCurrent())
170
+ return start;
171
+ setSession(start);
172
+ logger.log('[funding] track() start', { sessionId: start.id, status: start.status });
173
+ return await pollUntilTerminal(client, setSession, start, isCurrent);
174
+ }
175
+ catch (e) {
176
+ const err = e instanceof Error ? e : new Error(String(e));
177
+ if (isCurrent()) {
178
+ setError(err);
179
+ logger.error('[funding] track() failed', err);
180
+ }
181
+ throw err;
182
+ }
183
+ }, [client]);
184
+ const payLink = useCallback(async (params) => {
185
+ if (!client)
186
+ throw new Error('Funding is not configured (set uiConfig.fundingBaseUrl)');
187
+ return client.payLink(params);
188
+ }, [client]);
189
+ return {
190
+ session,
191
+ status: (_c = session === null || session === void 0 ? void 0 : session.status) !== null && _c !== void 0 ? _c : 'idle',
192
+ error,
193
+ loading,
194
+ isAvailable,
195
+ fund,
196
+ createSession,
197
+ track,
198
+ payLink,
199
+ reset,
200
+ };
201
+ }
202
+
203
+ export { useFunding };
204
+ //# sourceMappingURL=useFunding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFunding.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,49 @@
1
+ /** A source currency available on a chain (sourced live from Relay via the backend). */
2
+ export type FundingCurrency = {
3
+ symbol: string;
4
+ /** Contract address, or the zero address for the chain's native asset. */
5
+ address: string;
6
+ decimals: number;
7
+ logo: string | null;
8
+ /** True for the chain's native currency (ETH, SOL, POL, BNB, …). */
9
+ native: boolean;
10
+ };
11
+ /** A source chain the funding backend (Relay) can route from. */
12
+ export type FundingChain = {
13
+ /** CAIP-2 chain id, e.g. "eip155:8453". */
14
+ id: string;
15
+ name: string;
16
+ logo: string | null;
17
+ /** "evm" | "svm" — used to gate Solana sources and the CEX tab. */
18
+ vmType: string;
19
+ currencies: FundingCurrency[];
20
+ };
21
+ type UseFundingChains = {
22
+ chains: FundingChain[];
23
+ loading: boolean;
24
+ error: Error | null;
25
+ };
26
+ /**
27
+ * The chains/currencies the funding backend supports, fetched from
28
+ * `GET /v2/funding/chains` (a live passthrough of Relay's `/chains`), then
29
+ * narrowed to a curated subset. The provider dictionary is dynamic; the client
30
+ * just selects from it via `uiConfig.funding.{sourceChains,sourceCurrencies}`,
31
+ * defaulting to {@link DEFAULT_SOURCE_CHAINS} / {@link DEFAULT_SOURCE_CURRENCIES}.
32
+ *
33
+ * Reads the base URL from `uiConfig.fundingBaseUrl`, mirroring `useFunding`.
34
+ * Returns an empty list when funding isn't configured.
35
+ */
36
+ export declare function useFundingChains(): UseFundingChains;
37
+ /**
38
+ * Narrow the fetched chains to a selection. `sourceChains` is a CAIP-2 allowlist
39
+ * (also defines order); `sourceCurrencies` is a symbol allowlist where the
40
+ * sentinel `'native'` matches each chain's native currency. Chains left with no
41
+ * currency are dropped, and selected chains the rail doesn't support are skipped.
42
+ */
43
+ export declare function curateChains(chains: FundingChain[], sourceChains: string[] | undefined, sourceCurrencies: string[] | undefined): FundingChain[];
44
+ /** Nominal source amount (in base units) used only to mint an open, route-bound deposit address.
45
+ * The open address accepts any amount >= the route minimum afterward, so this is just a quote seed.
46
+ * Stablecoins use 1 whole unit (~$1, clears mainnet route minimums); native tokens are far pricier
47
+ * per unit (1 ETH ≈ thousands), so they use 0.01 of a unit to avoid minting/QR-prefilling 1 whole ETH. */
48
+ export declare function nominalUnits(decimals: number, native?: boolean): string;
49
+ export {};
@@ -0,0 +1,102 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useOpenfort } from '../../components/Openfort/useOpenfort.js';
3
+
4
+ /**
5
+ * Sensible default source chains — the common funding origins. Override with
6
+ * `uiConfig.funding.sourceChains`. Intersected with what the rail supports.
7
+ */
8
+ const DEFAULT_SOURCE_CHAINS = [
9
+ 'eip155:42161', // Arbitrum
10
+ 'eip155:8453', // Base
11
+ 'eip155:56', // BNB Chain
12
+ 'eip155:1', // Ethereum
13
+ 'eip155:10', // Optimism
14
+ 'eip155:137', // Polygon
15
+ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // Solana
16
+ ];
17
+ /**
18
+ * Default source currencies: the chain's `'native'` currency plus the major
19
+ * stablecoins. Override with `uiConfig.funding.sourceCurrencies`.
20
+ */
21
+ const DEFAULT_SOURCE_CURRENCIES = ['native', 'USDC', 'USDT'];
22
+ /**
23
+ * The chains/currencies the funding backend supports, fetched from
24
+ * `GET /v2/funding/chains` (a live passthrough of Relay's `/chains`), then
25
+ * narrowed to a curated subset. The provider dictionary is dynamic; the client
26
+ * just selects from it via `uiConfig.funding.{sourceChains,sourceCurrencies}`,
27
+ * defaulting to {@link DEFAULT_SOURCE_CHAINS} / {@link DEFAULT_SOURCE_CURRENCIES}.
28
+ *
29
+ * Reads the base URL from `uiConfig.fundingBaseUrl`, mirroring `useFunding`.
30
+ * Returns an empty list when funding isn't configured.
31
+ */
32
+ function useFundingChains() {
33
+ var _a, _b, _c, _d, _e;
34
+ const { uiConfig } = useOpenfort();
35
+ const baseUrl = (_a = uiConfig.fundingBaseUrl) !== null && _a !== void 0 ? _a : '';
36
+ const sourceChains = (_c = (_b = uiConfig.funding) === null || _b === void 0 ? void 0 : _b.sourceChains) !== null && _c !== void 0 ? _c : DEFAULT_SOURCE_CHAINS;
37
+ const sourceCurrencies = (_e = (_d = uiConfig.funding) === null || _d === void 0 ? void 0 : _d.sourceCurrencies) !== null && _e !== void 0 ? _e : DEFAULT_SOURCE_CURRENCIES;
38
+ const [state, setState] = useState({ chains: [], loading: Boolean(baseUrl), error: null });
39
+ useEffect(() => {
40
+ if (!baseUrl) {
41
+ setState({ chains: [], loading: false, error: null });
42
+ return;
43
+ }
44
+ let cancelled = false;
45
+ setState((s) => ({ ...s, loading: true }));
46
+ fetch(`${baseUrl}/v2/funding/chains`)
47
+ .then((r) => {
48
+ if (!r.ok)
49
+ throw new Error(`Failed to load chains (${r.status})`);
50
+ return r.json();
51
+ })
52
+ .then((d) => {
53
+ var _a;
54
+ if (!cancelled)
55
+ setState({ chains: (_a = d.chains) !== null && _a !== void 0 ? _a : [], loading: false, error: null });
56
+ })
57
+ .catch((e) => {
58
+ if (!cancelled)
59
+ setState({ chains: [], loading: false, error: e instanceof Error ? e : new Error(String(e)) });
60
+ });
61
+ return () => {
62
+ cancelled = true;
63
+ };
64
+ }, [baseUrl]);
65
+ // Narrow the provider dictionary to the selected subset (cheap, O(chains)).
66
+ return { ...state, chains: curateChains(state.chains, sourceChains, sourceCurrencies) };
67
+ }
68
+ /**
69
+ * Narrow the fetched chains to a selection. `sourceChains` is a CAIP-2 allowlist
70
+ * (also defines order); `sourceCurrencies` is a symbol allowlist where the
71
+ * sentinel `'native'` matches each chain's native currency. Chains left with no
72
+ * currency are dropped, and selected chains the rail doesn't support are skipped.
73
+ */
74
+ function curateChains(chains, sourceChains, sourceCurrencies) {
75
+ let out = chains;
76
+ if (sourceCurrencies && sourceCurrencies.length > 0) {
77
+ const allow = new Set(sourceCurrencies.map((s) => s.toLowerCase()));
78
+ const allowNative = allow.has('native');
79
+ out = out
80
+ .map((c) => ({
81
+ ...c,
82
+ currencies: c.currencies.filter((cur) => (allowNative && cur.native) || allow.has(cur.symbol.toLowerCase())),
83
+ }))
84
+ .filter((c) => c.currencies.length > 0);
85
+ }
86
+ if (sourceChains && sourceChains.length > 0) {
87
+ const byId = new Map(out.map((c) => [c.id, c]));
88
+ out = sourceChains.map((id) => byId.get(id)).filter((c) => c !== undefined);
89
+ }
90
+ return out;
91
+ }
92
+ /** Nominal source amount (in base units) used only to mint an open, route-bound deposit address.
93
+ * The open address accepts any amount >= the route minimum afterward, so this is just a quote seed.
94
+ * Stablecoins use 1 whole unit (~$1, clears mainnet route minimums); native tokens are far pricier
95
+ * per unit (1 ETH ≈ thousands), so they use 0.01 of a unit to avoid minting/QR-prefilling 1 whole ETH. */
96
+ function nominalUnits(decimals, native = false) {
97
+ const zeros = native ? Math.max(0, decimals - 2) : decimals;
98
+ return `1${'0'.repeat(zeros)}`;
99
+ }
100
+
101
+ export { curateChains, nominalUnits, useFundingChains };
102
+ //# sourceMappingURL=useFundingChains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFundingChains.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
3
3
  import { createPublicClient, http, formatEther } from 'viem';
4
4
  import { useOpenfort } from '../components/Openfort/useOpenfort.js';
5
5
  import { DEFAULT_TESTNET_CHAIN_ID } from '../core/ConnectionStrategy.js';
6
- import { useAsyncData } from '../shared/hooks/useAsyncData.js';
6
+ import { invalidateAsyncData, useAsyncData } from '../shared/hooks/useAsyncData.js';
7
7
  import { formatSol } from '../solana/hooks/utils.js';
8
8
  import { getDefaultEthereumRpcUrl, getDefaultSolanaRpcUrl, getNativeCurrency } from '../utils/rpc.js';
9
9
 
@@ -11,6 +11,11 @@ import { getDefaultEthereumRpcUrl, getDefaultSolanaRpcUrl, getNativeCurrency } f
11
11
  const BALANCE_INVALIDATE_EVENT = 'openfort:balance-invalidate';
12
12
  /** Dispatches balance invalidation so all useBalance instances refetch. Call after mint/send. */
13
13
  function invalidateBalance() {
14
+ // Token/asset balances come from useEthereumWalletAssets → useAsyncData, which
15
+ // doesn't listen to the event below. Drop their cache too so the next render
16
+ // (e.g. landing on the asset inventory after a deposit) refetches fresh.
17
+ invalidateAsyncData('walletAssets');
18
+ invalidateAsyncData('wallet-assets');
14
19
  if (typeof window !== 'undefined') {
15
20
  window.dispatchEvent(new CustomEvent(BALANCE_INVALIDATE_EVENT));
16
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useBalance.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useBalance.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/build/index.d.ts CHANGED
@@ -45,7 +45,7 @@ export { default as ChainIcon } from './components/Common/Chain';
45
45
  export { OpenfortButton } from './components/ConnectButton';
46
46
  export { OpenfortProvider } from './components/Openfort/OpenfortProvider';
47
47
  export type { CustomizableRoutes, MultiChainAsset } from './components/Openfort/types';
48
- export { LinkWalletOnSignUpOption, UIAuthProvider as AuthProvider } from './components/Openfort/types';
48
+ export { FundingMethod, LinkWalletOnSignUpOption, UIAuthProvider as AuthProvider } from './components/Openfort/types';
49
49
  export { embeddedWalletId } from './constants/openfort';
50
50
  export { OpenfortError, OpenfortReactErrorType, OpenfortReactErrorType as OpenfortErrorType, } from './core/errors';
51
51
  export type { ConnectedEmbeddedEthereumWallet, EthereumWalletState, SetActiveEthereumWalletOptions, UseEmbeddedEthereumWalletOptions, } from './ethereum/types';
@@ -58,7 +58,10 @@ export type { StoreCredentialsResult } from './hooks/openfort/auth/useOAuth';
58
58
  export { useOAuth } from './hooks/openfort/auth/useOAuth';
59
59
  export { usePhoneOtpAuth } from './hooks/openfort/auth/usePhoneOtpAuth';
60
60
  export { useSignOut } from './hooks/openfort/auth/useSignOut';
61
+ export type { FundingClient } from './hooks/openfort/fundingClient';
61
62
  export { type SignAuthorizationParameters, type SignAuthorizationReturnType, use7702Authorization, } from './hooks/openfort/use7702Authorization';
63
+ export type { FundingSession, FundingTarget, PaymentMethod, PaymentMethodInput, UseFunding, UseFundingOptions, } from './hooks/openfort/useFunding';
64
+ export { useFunding } from './hooks/openfort/useFunding';
62
65
  export { useGrantPermissions } from './hooks/openfort/useGrantPermissions';
63
66
  export { useRevokePermissions } from './hooks/openfort/useRevokePermissions';
64
67
  export { useUI } from './hooks/openfort/useUI';
package/build/index.js CHANGED
@@ -3,7 +3,7 @@ export { default as Avatar } from './components/Common/Avatar/index.js';
3
3
  export { default as ChainIcon } from './components/Common/Chain/index.js';
4
4
  export { OpenfortButton } from './components/ConnectButton/index.js';
5
5
  export { OpenfortProvider } from './components/Openfort/OpenfortProvider.js';
6
- export { UIAuthProvider as AuthProvider, LinkWalletOnSignUpOption } from './components/Openfort/types.js';
6
+ export { UIAuthProvider as AuthProvider, FundingMethod, LinkWalletOnSignUpOption } from './components/Openfort/types.js';
7
7
  export { embeddedWalletId } from './constants/openfort.js';
8
8
  export { OpenfortError, OpenfortReactErrorType as OpenfortErrorType, OpenfortReactErrorType } from './types.js';
9
9
  export { useAuthCallback } from './hooks/openfort/auth/useAuthCallback.js';
@@ -14,6 +14,7 @@ export { useOAuth } from './hooks/openfort/auth/useOAuth.js';
14
14
  export { usePhoneOtpAuth } from './hooks/openfort/auth/usePhoneOtpAuth.js';
15
15
  export { useSignOut } from './hooks/openfort/auth/useSignOut.js';
16
16
  export { use7702Authorization } from './hooks/openfort/use7702Authorization.js';
17
+ export { useFunding } from './hooks/openfort/useFunding.js';
17
18
  export { useGrantPermissions } from './hooks/openfort/useGrantPermissions.js';
18
19
  export { useRevokePermissions } from './hooks/openfort/useRevokePermissions.js';
19
20
  export { useUI } from './hooks/openfort/useUI.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -5,9 +5,20 @@ type UseAsyncDataOptions<T> = {
5
5
  refetchInterval?: number;
6
6
  staleTime?: number;
7
7
  };
8
+ /**
9
+ * Drop cached entries so the next mount (or refetch) hits the network instead of
10
+ * painting stale data. Pass a substring matched against the serialized queryKey
11
+ * to target specific queries (e.g. `'walletAssets'`), or omit to clear everything.
12
+ * Used by balance-changing flows (deposit/mint/send) to force fresh balances.
13
+ */
14
+ export declare function invalidateAsyncData(keyMatch?: string): void;
8
15
  /**
9
16
  * Simple fetch-with-cache hook. Replaces useQuery for internal SDK use.
10
17
  * No external dependency on TanStack Query.
18
+ *
19
+ * Stale-while-revalidate: a cached (non-empty) result for the same `queryKey` is
20
+ * painted synchronously on (re)mount — so navigating away and back is instant —
21
+ * then refreshed in the background (skipped while younger than `staleTime`).
11
22
  */
12
23
  export declare function useAsyncData<T>({ queryFn, queryKey, enabled, refetchInterval, staleTime, }: UseAsyncDataOptions<T>): {
13
24
  data: T | undefined;
@@ -1,29 +1,66 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
2
 
3
+ /** Module-level cache shared across hook instances, so revisiting a view paints instantly. */
4
+ const dataCache = new Map();
5
+ /**
6
+ * Drop cached entries so the next mount (or refetch) hits the network instead of
7
+ * painting stale data. Pass a substring matched against the serialized queryKey
8
+ * to target specific queries (e.g. `'walletAssets'`), or omit to clear everything.
9
+ * Used by balance-changing flows (deposit/mint/send) to force fresh balances.
10
+ */
11
+ function invalidateAsyncData(keyMatch) {
12
+ if (!keyMatch) {
13
+ dataCache.clear();
14
+ return;
15
+ }
16
+ for (const key of Array.from(dataCache.keys())) {
17
+ if (key.includes(keyMatch))
18
+ dataCache.delete(key);
19
+ }
20
+ }
21
+ /**
22
+ * Empty/absent results are NOT cached — only meaningful data is. This prevents a
23
+ * transient empty response (e.g. a cold/erroring asset call) from being pinned
24
+ * and shown as "no balance" on later mounts.
25
+ */
26
+ function isEmptyResult(result) {
27
+ if (result == null)
28
+ return true;
29
+ if (Array.isArray(result))
30
+ return result.length === 0;
31
+ return false;
32
+ }
3
33
  /**
4
34
  * Simple fetch-with-cache hook. Replaces useQuery for internal SDK use.
5
35
  * No external dependency on TanStack Query.
36
+ *
37
+ * Stale-while-revalidate: a cached (non-empty) result for the same `queryKey` is
38
+ * painted synchronously on (re)mount — so navigating away and back is instant —
39
+ * then refreshed in the background (skipped while younger than `staleTime`).
6
40
  */
7
41
  function useAsyncData({ queryFn, queryKey, enabled = true, refetchInterval, staleTime = 0, }) {
8
- const [data, setData] = useState(undefined);
42
+ // Serialize queryKey to a stable string so the effect only re-runs when values change,
43
+ // not when array/object references change.
44
+ const queryKeyStr = JSON.stringify(queryKey);
45
+ const [data, setData] = useState(() => { var _a; return (_a = dataCache.get(queryKeyStr)) === null || _a === void 0 ? void 0 : _a.data; });
9
46
  const [error, setError] = useState(null);
10
47
  const [isLoading, setIsLoading] = useState(false);
11
48
  const lastFetchRef = useRef(0);
12
49
  const intervalRef = useRef(null);
13
50
  const queryFnRef = useRef(queryFn);
14
51
  queryFnRef.current = queryFn;
15
- // Serialize queryKey to a stable string so the effect only re-runs when values change,
16
- // not when array/object references change.
17
- const queryKeyStr = JSON.stringify(queryKey);
18
- const fetchData = useCallback(async () => {
52
+ const fetchData = useCallback(async (showLoading = true) => {
19
53
  if (!enabled)
20
54
  return undefined;
21
- setIsLoading(true);
55
+ if (showLoading)
56
+ setIsLoading(true);
22
57
  setError(null);
23
58
  try {
24
59
  const result = await queryFnRef.current();
25
- setData(result);
60
+ if (!isEmptyResult(result))
61
+ dataCache.set(queryKeyStr, { data: result, timestamp: Date.now() });
26
62
  lastFetchRef.current = Date.now();
63
+ setData(result);
27
64
  return result;
28
65
  }
29
66
  catch (err) {
@@ -34,12 +71,22 @@ function useAsyncData({ queryFn, queryKey, enabled = true, refetchInterval, stal
34
71
  finally {
35
72
  setIsLoading(false);
36
73
  }
37
- }, [enabled]);
74
+ }, [enabled, queryKeyStr]);
38
75
  useEffect(() => {
39
76
  if (!enabled)
40
77
  return;
41
- fetchData().catch(() => { });
42
- }, [enabled, queryKeyStr]);
78
+ const cached = dataCache.get(queryKeyStr);
79
+ if (cached) {
80
+ // Paint cached data immediately; revalidate in the background unless still fresh.
81
+ setData(cached.data);
82
+ const fresh = staleTime > 0 && Date.now() - cached.timestamp < staleTime;
83
+ if (!fresh)
84
+ fetchData(false).catch(() => { });
85
+ }
86
+ else {
87
+ fetchData(true).catch(() => { });
88
+ }
89
+ }, [enabled, queryKeyStr, staleTime, fetchData]);
43
90
  useEffect(() => {
44
91
  if (!enabled || !refetchInterval || refetchInterval <= 0)
45
92
  return;
@@ -47,7 +94,7 @@ function useAsyncData({ queryFn, queryKey, enabled = true, refetchInterval, stal
47
94
  const elapsed = Date.now() - lastFetchRef.current;
48
95
  if (staleTime > 0 && elapsed < staleTime)
49
96
  return;
50
- fetchData().catch(() => { });
97
+ fetchData(false).catch(() => { });
51
98
  }, refetchInterval);
52
99
  return () => {
53
100
  if (intervalRef.current) {
@@ -61,9 +108,9 @@ function useAsyncData({ queryFn, queryKey, enabled = true, refetchInterval, stal
61
108
  error,
62
109
  isLoading,
63
110
  isPending: isLoading,
64
- refetch: fetchData,
111
+ refetch: useCallback(() => fetchData(true), [fetchData]),
65
112
  };
66
113
  }
67
114
 
68
- export { useAsyncData };
115
+ export { invalidateAsyncData, useAsyncData };
69
116
  //# sourceMappingURL=useAsyncData.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAsyncData.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useAsyncData.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,24 @@
1
+ /** A holding in the connected Solana wallet: native SOL or an SPL token. */
2
+ type SolanaAsset = {
3
+ /** Mint address, or 'native' for SOL. */
4
+ mint: string;
5
+ symbol: string;
6
+ name: string;
7
+ /** Raw balance in base units (lamports for SOL, token base units otherwise). */
8
+ amount: bigint;
9
+ decimals: number;
10
+ isNative: boolean;
11
+ };
12
+ /**
13
+ * Returns the connected Solana wallet's balances: native SOL plus SPL token
14
+ * holdings, read directly from the cluster RPC. The SVM counterpart of
15
+ * {@link useEthereumWalletAssets}, feeding the Solana asset inventory. Refreshes
16
+ * on BALANCE_INVALIDATE_EVENT (e.g. after a deposit/send).
17
+ */
18
+ export declare function useSolanaWalletAssets(): {
19
+ data: SolanaAsset[] | null;
20
+ isLoading: boolean;
21
+ isError: boolean;
22
+ refetch: () => Promise<unknown>;
23
+ };
24
+ export {};