@@ -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" ;
1514import { useEffect , useMemo , useRef , useState } from "react" ;
1615import { keyframes } from "styled-components" ;
16+ import { parseUnits } from "viem" ;
1717import { AlertIcon , WarningIcon } from "../../../assets/icons" ;
1818import { ROUTES } from "../../../constants/routes" ;
1919import { useRozoPay } from "../../../hooks/useDaimoPay" ;
2020import useIsMobile from "../../../hooks/useIsMobile" ;
2121import { usePayContext } from "../../../hooks/usePayContext" ;
22+ import { usePusherPayout } from "../../../hooks/usePusherPayout" ;
2223import styled from "../../../styles/styled" ;
2324import { formatUsd } from "../../../utils/format" ;
2425import 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 >
0 commit comments