Skip to content

Commit ea41834

Browse files
fix: deposit deeplink
1 parent 9444a39 commit ea41834

File tree

7 files changed

+144
-176
lines changed

7 files changed

+144
-176
lines changed

examples/nextjs-app/src/app/shared.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { RozoPayEvent, getChainExplorerByChainId } from "@rozoai/intent-common";
22
import { useEffect, useState } from "react";
33
import { isAddress } from "viem";
44

5-
export const APP_ID = "rozoBridgeStellar"; // Your public app ID. Use pay-demo for prototyping only.
5+
export const APP_ID = "rozoDemo"; // Your public app ID. Use pay-demo for prototyping only.
66

77
export const ROZOPAY_API_URL =
88
process.env.NEXT_PUBLIC_ROZOPAY_API_URL || "https://intentapi.rozo.ai";

packages/connectkit/bundle-analysis.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/connectkit/src/components/Pages/WaitingDepositAddress/index.tsx

Lines changed: 91 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import {
66
getChainName,
77
getFee,
88
getKnownToken,
9-
getPayment,
109
isHydrated,
1110
type FeeErrorData,
1211
type FeeResponseData,
1312
type Token,
1413
} from "@rozoai/intent-common";
1514
import { useEffect, useMemo, useRef, useState } from "react";
1615
import { keyframes } from "styled-components";
16+
import { parseUnits } from "viem";
1717
import { AlertIcon, WarningIcon } from "../../../assets/icons";
1818
import { ROUTES } from "../../../constants/routes";
1919
import { useRozoPay } from "../../../hooks/useDaimoPay";
2020
import useIsMobile from "../../../hooks/useIsMobile";
2121
import { usePayContext } from "../../../hooks/usePayContext";
22+
import { usePusherPayout } from "../../../hooks/usePusherPayout";
2223
import styled from "../../../styles/styled";
2324
import { formatUsd } from "../../../utils/format";
2425
import Button from "../../Common/Button";
@@ -98,150 +99,61 @@ export default function WaitingDepositAddress() {
9899
const [isLoading, setIsLoading] = useState(false);
99100
const [depoChain, setDepoChain] = useState<string>();
100101
const [hasExecutedDepositCall, setHasExecutedDepositCall] = useState(false);
101-
const [payinTransactionHash, setPayinTransactionHash] = useState<
102-
string | null
103-
>(null);
104102
const [feeData, setFeeData] = useState<FeeResponseData | null>(null);
105103
const [feeError, setFeeError] = useState<FeeErrorData | null>(null);
106104

107-
const [isPollingPayment, setIsPollingPayment] = useState(false);
108-
109-
useEffect(() => {
110-
if (rozoPaymentState === "error") {
111-
context.setRoute(ROUTES.ERROR);
112-
}
113-
}, [rozoPaymentState]);
114-
115-
// Safe polling for payment status when externalId exists
116-
const shouldPoll = !!(
105+
// Use Pusher to detect payin in real-time
106+
const pusherEnabled = !!(
117107
depAddr?.externalId &&
118-
!payinTransactionHash &&
119-
depAddr?.expirationS
108+
(rozoPaymentId || depAddr?.externalId) &&
109+
rozoPaymentState !== "payment_started" &&
110+
rozoPaymentState !== "payment_completed"
120111
);
121112

122-
// Polling effect - check every 5 seconds for payinTransactionHash (only during countdown)
123-
useEffect(() => {
124-
if (!shouldPoll) {
125-
setIsPollingPayment(false);
126-
return;
127-
}
128-
129-
// Check if countdown is still active
130-
const isCountdownActive = () => {
131-
if (!depAddr?.expirationS) return false;
132-
const remainingTime = depAddr.expirationS - Date.now() / 1000;
133-
return remainingTime > 0;
134-
};
135-
136-
if (!isCountdownActive()) {
137-
context.log("[PAYMENT POLLING] Countdown expired, stopping polling");
138-
setIsPollingPayment(false);
139-
return;
140-
}
141-
142-
context.log(
143-
"[PAYMENT POLLING] Starting payment polling for externalId:",
144-
depAddr?.externalId
145-
);
146-
setIsPollingPayment(true);
147-
148-
let isActive = true;
149-
let timeoutId: NodeJS.Timeout;
150-
151-
const pollPayment = async () => {
152-
context.log("[PAYMENT POLLING] Polling for payment transaction:", {
153-
isActive,
154-
externalId: depAddr?.externalId,
155-
isCountdownActive: isCountdownActive(),
156-
});
157-
158-
if (!isActive || !depAddr?.externalId) {
113+
usePusherPayout({
114+
enabled: pusherEnabled,
115+
rozoPaymentId: rozoPaymentId || depAddr?.externalId,
116+
onPayinDetected: (payload) => {
117+
if (payload.source_txhash && selectedDepositAddressOption) {
159118
context.log(
160-
"[PAYMENT POLLING] No active polling or missing externalId, stopping polling"
119+
"[PAYIN DETECTED] Payment received via Pusher:",
120+
payload.source_txhash
161121
);
162-
return;
163-
}
164-
165-
// Stop polling if countdown expired
166-
if (!isCountdownActive()) {
167-
context.log(
168-
"[PAYMENT POLLING] Countdown expired during polling, stopping"
169-
);
170-
setIsPollingPayment(false);
171-
return;
172-
}
173-
174-
try {
175-
context.log(
176-
"[PAYMENT POLLING] Polling for payment transaction:",
177-
depAddr?.externalId
122+
setPaymentCompleted(
123+
payload.source_txhash,
124+
rozoPaymentId || payload.payment_id
178125
);
179-
const isMugglePay = depAddr?.externalId.includes("mugglepay_order");
180-
const response = await getPayment(depAddr?.externalId, "v2");
181-
182-
context.log("[PAYMENT POLLING] Debug - API Response:", {
183-
status: response.status,
184-
hasData: !!response.data,
185-
hasError: !!response.error,
186-
errorMessage: response.error?.message,
187-
payinTransactionHash: response.data?.payinTransactionHash || null,
188-
fullData: response.data,
189-
});
190-
191-
const payInHash = isMugglePay
192-
? response.data?.metadata?.source_tx_hash
193-
: response.data?.payinTransactionHash;
194-
195-
if (isActive && response.data && payInHash) {
196-
context.log(
197-
"[PAYMENT POLLING] ✅ Found payinTransactionHash:",
198-
payInHash
199-
);
200-
setPayinTransactionHash(payInHash as string);
201-
setIsPollingPayment(false);
202-
// TODO: Decide which route to navigate to when transaction hash is found
203-
context.log(
204-
"[PAYMENT POLLING] 🎉 Payment confirmed - ready to navigate to next step"
205-
);
206-
return;
207-
}
208-
209-
context.log(
210-
"[PAYMENT POLLING] ⏳ Payment not yet confirmed, scheduling next poll"
126+
setPaymentPayoutCompleted(
127+
payload.source_txhash,
128+
rozoPaymentId || payload.payment_id
211129
);
212-
// Schedule next poll
213-
if (isActive && isCountdownActive()) {
214-
timeoutId = setTimeout(pollPayment, 7000);
215-
}
216-
} catch (error) {
217-
console.error("[PAYMENT POLLING] ❌ Error during polling:", error);
218-
// Continue polling on error, but only if countdown is still active
219-
if (isActive && isCountdownActive()) {
220-
timeoutId = setTimeout(pollPayment, 7000);
221-
}
130+
const tokenMode =
131+
selectedDepositAddressOption?.id ===
132+
DepositAddressPaymentOptions.SOLANA
133+
? "solana"
134+
: selectedDepositAddressOption?.id ===
135+
DepositAddressPaymentOptions.STELLAR
136+
? "stellar"
137+
: "evm";
138+
setTokenMode(tokenMode);
139+
setTxHash(payload.source_txhash);
140+
context.setRoute(ROUTES.CONFIRMATION);
222141
}
223-
};
142+
},
143+
onDataReceived: () => {
144+
context.log("[PUSHER] Data received for deposit address payment");
145+
},
146+
log: context.log,
147+
});
224148

225-
// Start polling immediately
226-
timeoutId = setTimeout(pollPayment, 0);
227-
228-
// Cleanup on unmount or when dependencies change
229-
return () => {
230-
context.log(
231-
"[PAYMENT POLLING] 🧹 Cleaning up polling for:",
232-
depAddr?.externalId || "unknown"
233-
);
234-
isActive = false;
235-
setIsPollingPayment(false);
236-
if (timeoutId) {
237-
clearTimeout(timeoutId);
238-
}
239-
};
240-
}, [shouldPoll, depAddr?.expirationS, depAddr?.externalId]);
149+
useEffect(() => {
150+
if (rozoPaymentState === "error") {
151+
context.setRoute(ROUTES.ERROR);
152+
}
153+
}, [rozoPaymentState]);
241154

242155
// If we selected a deposit address option, generate the address...
243156
const generateDepositAddress = async () => {
244-
console.log("selectedDepositAddressOption", selectedDepositAddressOption);
245157
if (selectedDepositAddressOption == null) {
246158
if (order == null || !isHydrated(order)) return;
247159
if (order.sourceTokenAmount == null) return;
@@ -267,13 +179,27 @@ export default function WaitingDepositAddress() {
267179
expirationS = Number(order.expirationTs);
268180
}
269181

270-
console.log("order", order);
182+
if (!order.preferredChainId || !order.preferredTokenAddress) {
183+
throw new Error("Preferred chain or token address not found");
184+
}
185+
186+
const preferredToken = getKnownToken(
187+
Number(order.preferredChainId),
188+
order.preferredTokenAddress
189+
);
190+
191+
if (!preferredToken) {
192+
throw new Error("Preferred token not found");
193+
}
271194

272-
const evmDeepLink = generateEVMDeepLink({
273-
amountUnits: order.destFinalCallTokenAmount.amount,
274-
chainId: order.destFinalCallTokenAmount.token.chainId,
195+
const evmDeeplink = generateEVMDeepLink({
196+
amountUnits: parseUnits(
197+
order.destFinalCallTokenAmount.usd.toString(),
198+
preferredToken.decimals
199+
).toString(),
200+
chainId: preferredToken.chainId,
275201
recipientAddress: order.intentAddr,
276-
tokenAddress: order.destFinalCallTokenAmount.token.token,
202+
tokenAddress: preferredToken.token,
277203
});
278204

279205
setDepAddr({
@@ -283,11 +209,11 @@ export default function WaitingDepositAddress() {
283209
unitsPaid: order.destFinalCallTokenAmount.amount,
284210
coin: order.destFinalCallTokenAmount.token.symbol,
285211
},
286-
coins: `${
287-
order.destFinalCallTokenAmount.token.symbol
288-
} on ${getChainName(order.destFinalCallTokenAmount.token.chainId)}`,
212+
coins: `${preferredToken.symbol} on ${getChainName(
213+
preferredToken.chainId
214+
)}`,
289215
expirationS: expirationS,
290-
uri: evmDeepLink,
216+
uri: evmDeeplink,
291217
displayToken: order.destFinalCallTokenAmount.token,
292218
logoURI: "", // Not needed for underpaid orders
293219
memo: order.memo || order.metadata?.memo || undefined,
@@ -395,13 +321,10 @@ export default function WaitingDepositAddress() {
395321
});
396322
setRozoPaymentId(details.externalId);
397323
setDepoChain(selectedDepositAddressOption.id);
398-
399-
// Polling will automatically start via shouldPoll calculation
400324
} else if (details === null) {
401325
// Duplicate call was prevented - reset loading states
402326
setIsLoading(false);
403327
setHasExecutedDepositCall(false);
404-
// Polling will automatically stop when externalId is missing
405328
return;
406329
} else {
407330
setFailed(true);
@@ -418,10 +341,8 @@ export default function WaitingDepositAddress() {
418341
// Track which deposit option we're currently processing to prevent double execution
419342
const processingOptionRef = useRef<string | null>(null);
420343

421-
// Reset payment hash when deposit option changes
344+
// Reset execution flag when deposit option changes
422345
useEffect(() => {
423-
setPayinTransactionHash(null);
424-
425346
// Reset execution flag when selectedDepositAddressOption changes
426347
if (selectedDepositAddressOption) {
427348
setHasExecutedDepositCall(false);
@@ -478,14 +399,18 @@ export default function WaitingDepositAddress() {
478399
// eslint-disable-next-line react-hooks/exhaustive-deps
479400
useEffect(triggerResize, [depAddr, failed, feeData, feeError]);
480401

481-
// Completed payment effect
402+
// Navigate to confirmation when payment state changes to started or completed
482403
useEffect(() => {
483-
if (payinTransactionHash && selectedDepositAddressOption) {
404+
if (
405+
(rozoPaymentState === "payment_started" ||
406+
rozoPaymentState === "payment_completed") &&
407+
selectedDepositAddressOption &&
408+
order &&
409+
isHydrated(order)
410+
) {
484411
context.log(
485-
"[PAYMENT COMPLETED] Payment completed, navigating to next step"
412+
"[PAYMENT] Payment state changed, navigating to confirmation"
486413
);
487-
setPaymentCompleted(payinTransactionHash, rozoPaymentId);
488-
setPaymentPayoutCompleted(payinTransactionHash, rozoPaymentId);
489414
const tokenMode =
490415
selectedDepositAddressOption?.id === DepositAddressPaymentOptions.SOLANA
491416
? "solana"
@@ -494,10 +419,24 @@ export default function WaitingDepositAddress() {
494419
? "stellar"
495420
: "evm";
496421
setTokenMode(tokenMode);
497-
setTxHash(payinTransactionHash);
422+
423+
// Extract transaction hash from order if available
424+
const txHash = order.sourceStartTxHash || order.sourceInitiateTxHash;
425+
426+
if (txHash) {
427+
setTxHash(txHash);
428+
}
429+
498430
context.setRoute(ROUTES.CONFIRMATION);
499431
}
500-
}, [payinTransactionHash]);
432+
}, [
433+
rozoPaymentState,
434+
selectedDepositAddressOption,
435+
order,
436+
context,
437+
setTokenMode,
438+
setTxHash,
439+
]);
501440

502441
return (
503442
<PageContent>

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.1,
76+
minimumUsd: 0.01,
7777
chainId: base.chainId,
7878
token: baseUSDC,
7979
},

packages/connectkit/src/hooks/usePaymentState.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
generateEVMDeepLink,
2222
generateIntentTitle,
2323
getChainById,
24+
getKnownToken,
2425
isValidSolanaAddress,
2526
mergedMetadata,
2627
PaymentResponse,
@@ -1122,11 +1123,27 @@ export function usePaymentState({
11221123

11231124
log?.(`[PAY DEPOSIT ADDRESS] order: ${debugJson(order)}`);
11241125

1126+
if (!order.preferredChainId || !order.preferredTokenAddress) {
1127+
throw new Error("Preferred chain or token address not found");
1128+
}
1129+
1130+
const preferredToken = getKnownToken(
1131+
Number(order.preferredChainId),
1132+
order.preferredTokenAddress
1133+
);
1134+
1135+
if (!preferredToken) {
1136+
throw new Error("Preferred token not found");
1137+
}
1138+
11251139
const evmDeeplink = generateEVMDeepLink({
1126-
amountUnits: order.destFinalCallTokenAmount.amount,
1127-
chainId: order.destFinalCallTokenAmount.token.chainId,
1140+
amountUnits: parseUnits(
1141+
order.destFinalCallTokenAmount.usd.toString(),
1142+
preferredToken.decimals
1143+
).toString(),
1144+
chainId: preferredToken.chainId,
11281145
recipientAddress: order.intentAddr,
1129-
tokenAddress: order.destFinalCallTokenAmount.token.token,
1146+
tokenAddress: preferredToken.token,
11301147
});
11311148

11321149
return {

0 commit comments

Comments
 (0)