Skip to content

Commit db4e352

Browse files
feat: clear cache payment options on payment completed
1 parent 377b166 commit db4e352

File tree

9 files changed

+77
-122
lines changed

9 files changed

+77
-122
lines changed

packages/connectkit/src/components/Pages/Stellar/PayWithStellarToken/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const PayWithStellarToken: React.FC = () => {
4545
const {
4646
selectedStellarTokenOption,
4747
payWithStellarToken,
48+
clearPaymentOptionsCache,
4849
setTxHash,
4950
payParams,
5051
rozoPaymentId,
@@ -248,6 +249,7 @@ const PayWithStellarToken: React.FC = () => {
248249
);
249250

250251
if (response.successful) {
252+
clearPaymentOptionsCache();
251253
setPayState(PayState.RequestSuccessful);
252254
setTxHash(response.hash);
253255
setTxURL(getChainExplorerTxUrl(stellar.chainId, response.hash));

packages/connectkit/src/hooks/useDepositAddressOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function useDepositAddressOptions({
7373
{
7474
id: DepositAddressPaymentOptions.BASE_USDC,
7575
logoURI: baseUSDC.logoURI,
76-
minimumUsd: 0.01,
76+
minimumUsd: 0.1,
7777
chainId: base.chainId,
7878
token: baseUSDC,
7979
},

packages/connectkit/src/hooks/usePaymentState.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ export interface PaymentState {
223223
option: WalletPaymentOption,
224224
store: Store<PaymentState, PaymentEvent>
225225
) => Promise<PaymentResponse | undefined>;
226+
227+
// Clear payment options cache to prevent stale balances
228+
clearPaymentOptionsCache: () => void;
226229
}
227230

228231
export function usePaymentState({
@@ -720,6 +723,7 @@ export function usePaymentState({
720723

721724
// Set transaction hash and return result
722725
setTxHash(paymentTxHash);
726+
clearPaymentOptionsCache();
723727
return { txHash: paymentTxHash, success: true };
724728
};
725729

@@ -962,6 +966,7 @@ export function usePaymentState({
962966
const tx = Transaction.from(bs58.decode(serializedTransaction));
963967
const txHash = await solanaWallet.sendTransaction(tx, connection);
964968
log("[PAY SOLANA] Transaction sent! Hash:", txHash);
969+
clearPaymentOptionsCache();
965970
return { txHash: txHash, success: true };
966971
} catch (error) {
967972
console.error(`[PAY SOLANA] Error: ${error}`);
@@ -1278,6 +1283,12 @@ export function usePaymentState({
12781283
await pay.createPreviewOrder(currPayParams);
12791284
};
12801285

1286+
const clearPaymentOptionsCache = () => {
1287+
walletPaymentOptions.clearCache();
1288+
solanaPaymentOptions.clearCache();
1289+
stellarPaymentOptions.clearCache();
1290+
};
1291+
12811292
const resetOrder = useCallback(
12821293
async (payParams?: Partial<PayParams>) => {
12831294
const mergedPayParams: PayParams | undefined =
@@ -1382,5 +1393,6 @@ export function usePaymentState({
13821393
solanaPubKey,
13831394
stellarPubKey,
13841395
orderUsdAmount,
1396+
clearPaymentOptionsCache,
13851397
};
13861398
}

packages/connectkit/src/hooks/useSolanaPaymentOptions.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export function useSolanaPaymentOptions({
3131
// Track if we're currently making an API call to prevent concurrent requests
3232
const isApiCallInProgress = useRef<boolean>(false);
3333

34+
// Track if we have initial data to prevent clearing options on refresh (prevents flickering)
35+
const hasInitialData = useRef<boolean>(false);
36+
3437
const stableAppId = useMemo(() => {
3538
return payParams?.appId;
3639
}, [payParams]);
@@ -66,8 +69,15 @@ export function useSolanaPaymentOptions({
6669
const fetchBalances = useCallback(async () => {
6770
if (address == null || usdRequired == null || stableAppId == null) return;
6871

69-
setOptions(null);
70-
setIsLoading(true);
72+
// Only clear options if we don't have initial data yet to prevent flickering
73+
// If we already have options, keep them visible while loading new data
74+
if (!hasInitialData.current) {
75+
setOptions(null);
76+
setIsLoading(true);
77+
} else {
78+
// Keep existing options visible, just set loading state
79+
setIsLoading(true);
80+
}
7181

7282
try {
7383
const newOptions = await trpc.getSolanaPaymentOptions.query({
@@ -77,9 +87,13 @@ export function useSolanaPaymentOptions({
7787
appId: stableAppId,
7888
});
7989
setOptions(newOptions);
90+
hasInitialData.current = true;
8091
} catch (error) {
8192
console.error(error);
82-
setOptions([]);
93+
// Only set empty array if we don't have initial data
94+
if (!hasInitialData.current) {
95+
setOptions([]);
96+
}
8397
} finally {
8498
isApiCallInProgress.current = false;
8599
setIsLoading(false);
@@ -92,6 +106,22 @@ export function useSolanaPaymentOptions({
92106
isApiCallInProgress,
93107
});
94108

109+
// Clear cache function to reset balance state and tracking
110+
// This is called when payment completes for Solana tokens to prevent stale balances
111+
const clearCache = useCallback(() => {
112+
setOptions(null);
113+
lastExecutedParams.current = null;
114+
isApiCallInProgress.current = false;
115+
hasInitialData.current = false;
116+
}, []);
117+
118+
// Reset hasInitialData when address changes to allow clearing options for new address
119+
useEffect(() => {
120+
if (address) {
121+
hasInitialData.current = false;
122+
}
123+
}, [address]);
124+
95125
// Smart clearing: only clear if we don't have data for this address
96126
useEffect(() => {
97127
if (address && !options) {
@@ -132,11 +162,13 @@ export function useSolanaPaymentOptions({
132162
if (address != null && usdRequired != null && stableAppId != null) {
133163
refreshOptions();
134164
}
135-
}, [address, usdRequired, stableAppId]);
165+
// refreshOptions is stable (created from fetchBalances which only changes when dependencies change)
166+
}, [address, usdRequired, stableAppId]); // eslint-disable-line react-hooks/exhaustive-deps
136167

137168
return {
138169
options: filteredOptions,
139170
isLoading,
140171
refreshOptions,
172+
clearCache,
141173
};
142174
}

packages/connectkit/src/hooks/useStellarPaymentOptions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export function useStellarPaymentOptions({
9292
isApiCallInProgress,
9393
});
9494

95+
// Clear cache function to reset balance state and tracking
96+
// This is called when payment completes for Stellar tokens to prevent stale balances
97+
const clearCache = useCallback(() => {
98+
setOptions(null);
99+
lastExecutedParams.current = null;
100+
isApiCallInProgress.current = false;
101+
}, []);
102+
95103
// Smart clearing: only clear if we don't have data for this address
96104
useEffect(() => {
97105
if (address && !options) {
@@ -138,5 +146,6 @@ export function useStellarPaymentOptions({
138146
options: filteredOptions,
139147
isLoading,
140148
refreshOptions,
149+
clearCache,
141150
};
142151
}

packages/connectkit/src/hooks/useTokenOptions.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,13 @@ export function useTokenOptions(mode: "evm" | "solana" | "stellar" | "all"): {
123123
)
124124
: solanaOptionsRaw;
125125

126-
const solanaOptions = solanaPaymentOptions.isLoading
127-
? []
128-
: getSolanaTokenOptions(
129-
filteredSolanaOptions,
130-
isDepositFlow,
131-
setSelectedSolanaTokenOption,
132-
setRoute
133-
);
126+
// Don't clear options when loading - keep existing options visible to prevent flickering
127+
const solanaOptions = getSolanaTokenOptions(
128+
filteredSolanaOptions,
129+
isDepositFlow,
130+
setSelectedSolanaTokenOption,
131+
setRoute
132+
);
134133
optionsList.push(...solanaOptions);
135134
isLoading ||= solanaPaymentOptions.isLoading;
136135
hasAnyData ||= filteredSolanaOptions.length > 0;

packages/connectkit/src/hooks/useWalletPaymentOptions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ export function useWalletPaymentOptions({
177177
isApiCallInProgress,
178178
});
179179

180+
// Clear cache function to reset balance state and tracking
181+
// This is called when payment completes for EVM tokens to prevent stale balances
182+
const clearCache = useCallback(() => {
183+
setOptions(null);
184+
lastExecutedParams.current = null;
185+
isApiCallInProgress.current = false;
186+
}, []);
187+
180188
// Initial fetch when hook mounts with valid parameters or when key parameters change
181189
useEffect(() => {
182190
if (
@@ -193,5 +201,6 @@ export function useWalletPaymentOptions({
193201
options: filteredOptions,
194202
isLoading,
195203
refreshOptions,
204+
clearCache,
196205
};
197206
}

packages/pay-common/README.md

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -316,76 +316,6 @@ Based on [ROZO API Documentation](https://docs.rozo.ai/integration/api-doc/suppo
316316
| Base | `8453` | `base` | EVM ||
317317
| Rozo Stellar | `1500` | `rozoStellar` | Stellar | ✅ (7 dec) |
318318

319-
### Token Addresses
320-
321-
<details>
322-
<summary><strong>Ethereum (Chain ID: 1)</strong></summary>
323-
324-
| Token | Address | Decimals | Constant |
325-
| ----- | -------------------------------------------- | -------- | -------------- |
326-
| USDC | `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | 6 | `ethereumUSDC` |
327-
| USDT | `0xdac17f958d2ee523a2206206994597c13d831ec7` | 6 | `ethereumUSDT` |
328-
329-
</details>
330-
331-
<details>
332-
<summary><strong>Arbitrum (Chain ID: 42161)</strong></summary>
333-
334-
| Token | Address | Decimals | Constant |
335-
| ----- | -------------------------------------------- | -------- | -------------- |
336-
| USDC | `0xaf88d065e77c8cc2239327c5edb3a432268e5831` | 6 | `arbitrumUSDC` |
337-
| USDT | `0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9` | 6 | `arbitrumUSDT` |
338-
339-
</details>
340-
341-
<details>
342-
<summary><strong>Base (Chain ID: 8453)</strong></summary>
343-
344-
| Token | Address | Decimals | Constant |
345-
| ----- | -------------------------------------------- | -------- | ---------- |
346-
| USDC | `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913` | 6 | `baseUSDC` |
347-
348-
</details>
349-
350-
<details>
351-
<summary><strong>BSC (Chain ID: 56)</strong></summary>
352-
353-
| Token | Address | Decimals | Constant |
354-
| ----- | -------------------------------------------- | -------- | --------- |
355-
| USDC | `0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d` | 18 | `bscUSDC` |
356-
| USDT | `0x55d398326f99059ff775485246999027b3197955` | 18 | `bscUSDT` |
357-
358-
</details>
359-
360-
<details>
361-
<summary><strong>Polygon (Chain ID: 137)</strong></summary>
362-
363-
| Token | Address | Decimals | Constant |
364-
| ----- | -------------------------------------------- | -------- | ------------- |
365-
| USDC | `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359` | 6 | `polygonUSDC` |
366-
| USDT | `0xc2132d05d31c914a87c6611c10748aeb04b58e8f` | 6 | `polygonUSDT` |
367-
368-
</details>
369-
370-
<details>
371-
<summary><strong>Rozo Solana (Chain ID: 900)</strong></summary>
372-
373-
| Token | Address | Decimals | Constant |
374-
| ----- | ---------------------------------------------- | -------- | ---------------- |
375-
| USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | 6 | `rozoSolanaUSDC` |
376-
| USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | 6 | `rozoSolanaUSDT` |
377-
378-
</details>
379-
380-
<details>
381-
<summary><strong>Rozo Stellar (Chain ID: 1500)</strong></summary>
382-
383-
| Token | Address | Decimals | Constant |
384-
| ----- | --------------------------------------------------------------- | -------- | ----------------- |
385-
| USDC | `USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN` | 7 | `rozoStellarUSDC` |
386-
387-
</details>
388-
389319
---
390320

391321
## 🛠️ Utility Functions
@@ -437,44 +367,6 @@ console.log(config.preferred); // Source payment details
437367
console.log(config.destination); // Destination payment details
438368
```
439369

440-
---
441-
442-
## 🔐 Webhook Verification
443-
444-
When using webhooks, verify incoming requests using HMAC-SHA256:
445-
446-
```typescript
447-
import crypto from "crypto";
448-
449-
function verifyWebhook(
450-
payload: string,
451-
signature: string,
452-
secret: string
453-
): boolean {
454-
const hmac = crypto.createHmac("sha256", secret);
455-
const digest = hmac.update(payload).digest("hex");
456-
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
457-
}
458-
459-
// In your webhook handler
460-
app.post("/api/webhooks/payment", (req, res) => {
461-
const signature = req.headers["x-rozo-signature"];
462-
const payload = JSON.stringify(req.body);
463-
464-
if (!verifyWebhook(payload, signature, payment.webhookSecret)) {
465-
return res.status(401).send("Invalid signature");
466-
}
467-
468-
// Process webhook
469-
const { status, id } = req.body;
470-
console.log(`Payment ${id} status: ${status}`);
471-
472-
res.status(200).send("OK");
473-
});
474-
```
475-
476-
---
477-
478370
## 📚 TypeScript Support
479371

480372
Full TypeScript definitions included. All exports are typed.

packages/pay-common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rozoai/intent-common",
3-
"version": "0.1.6-beta.3",
3+
"version": "0.1.6",
44
"description": "Intent Pay shared types and utilities",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

0 commit comments

Comments
 (0)