Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/nextjs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
},
"dependencies": {
"@creit.tech/stellar-wallets-kit": "^1.9.5",
"@rozoai/intent-common": "0.1.23",
"@rozoai/intent-pay": "0.1.36",
"@rozoai/intent-common": "0.1.25",
"@rozoai/intent-pay": "0.1.37",
"@stellar/stellar-sdk": "^14.6.1",
"@tanstack/react-query": "^5.0.0",
"class-variance-authority": "^0.7.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/connectkit/bundle-analysis.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/connectkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rozoai/intent-pay",
"version": "0.1.36",
"version": "0.1.37",
"private": false,
"description": "Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.",
"keywords": [
Expand Down Expand Up @@ -48,7 +48,7 @@
"@reown/appkit": "^1.7.0",
"@rollup/plugin-image": "^3.0.3",
"@rollup/plugin-typescript": "^12.1.2",
"@rozoai/intent-common": "0.1.23",
"@rozoai/intent-common": "0.1.25",
"@solana/spl-memo": "^0.2.5",
"@solana/spl-token": "^0.4.14",
"@solana/wallet-adapter-base": "^0.9.27",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ const ConnectorItem = ({

const onClick = () => {
const meta = { event: "connector-list-click", walletId: wallet.id };
context.setUserDisconnected(false);

// Desktop multi-chain wallet flow: prompt for chain selection.
if (!isMobile) {
Expand Down
17 changes: 15 additions & 2 deletions packages/connectkit/src/components/Common/OptionsList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { motion } from "framer-motion";
import { useEffect } from "react";
import { keyframes } from "styled-components";
import { Spinner } from "../Spinner";
import { SpinnerContainer } from "../Spinner/styles";
import { usePayContext } from "../../../hooks/usePayContext";
import styled from "../../../styles/styled";
import { OrDivider } from "../Modal";
Expand All @@ -18,6 +20,7 @@ export interface Option {
sortValue?: number;
title: string;
subtitle?: string;
loading?: boolean;
icons: (React.ReactNode | string)[];
rightIcons?: (React.ReactNode | string)[];
iconsPosition?: "left" | "right";
Expand Down Expand Up @@ -180,15 +183,25 @@ const OptionItem = ({ option }: { option: Option }) => {
onClick={option.onClick}
disabled={option.disabled}
>
{iconContent}
<OptionLabel
$hasRightIcons={hydratedRightIcons.length > 0}
$iconsPosition={iconsPosition}
>
<OptionTitle>{option.title}</OptionTitle>
{option.subtitle && <OptionSubtitle>{option.subtitle}</OptionSubtitle>}
</OptionLabel>
{rightIconContent}
{option.loading ? (
<IconStackContainer $position="right">
<SpinnerContainer>
<Spinner />
</SpinnerContainer>
</IconStackContainer>
) : (
<>
{iconContent}
{rightIconContent}
</>
)}
</OptionButton>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,16 @@ const MobileConnectors: React.FC = () => {
if (wallet.isSolanaOnly && !paymentState.showSolanaPaymentMethod) return false;
if (wallet.isStellarOnly && !paymentState.showStellarPaymentMethod) return false;
// Skip if already shown as injected — cross-match name/shortName
const cfgName = (wallet.name ?? wallet.shortName ?? "").toLowerCase();
const cfgNames = [wallet.name, wallet.shortName]
.filter(Boolean)
.map((n) => n!.toLowerCase());
const nameMatch =
cfgName &&
cfgNames.length > 0 &&
injectedWallets.some((iw) => {
const iwName = (iw.name ?? iw.shortName ?? "").toLowerCase();
return iwName && iwName === cfgName;
const iwNames = [iw.name, iw.shortName]
.filter(Boolean)
.map((n) => n!.toLowerCase());
return iwNames.some((iwn) => cfgNames.includes(iwn));
});
if (nameMatch) return false;
return true;
Expand All @@ -88,6 +92,7 @@ const MobileConnectors: React.FC = () => {
);

const handleInjectedWallet = (wallet: WalletProps) => {
context.setUserDisconnected(false);
if (wallet.connector && wallet.solanaConnectorName) {
// Dual-chain (e.g. Phantom in-app browser)
context.setDualChainConnect(true);
Expand All @@ -101,6 +106,7 @@ const MobileConnectors: React.FC = () => {
};

const handleDeeplinkWallet = (wallet: WalletConfigProps) => {
context.setUserDisconnected(false);
if (!orderReady) return; // still hydrating — ignore tap
if (wallet.getRozoPayDeeplink == null) {
console.error(`wallet ${wallet.name} has no deeplink`);
Expand Down
30 changes: 19 additions & 11 deletions packages/connectkit/src/components/Pages/SelectMethod/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef } from "react";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { ROUTES } from "../../../constants/routes";
import { usePayContext } from "../../../hooks/usePayContext";
import { useAnalytics } from "../../../provider/AnalyticsProvider";
Expand Down Expand Up @@ -54,12 +54,21 @@ export default function SelectMethod() {
isExternalKit: isStellarExternalKit,
} = useStellar();

const { setRoute, paymentState, log, disableMobileInjector, open: modalOpen } = usePayContext();
const { setRoute, paymentState, log, disableMobileInjector, open: modalOpen, setUserDisconnected } = usePayContext();
const { capture } = useAnalytics();
const { showSolanaPaymentMethod } = paymentState;
const { disconnectAsync } = useDisconnect();
const autoConnectGate = useAutoConnectGate();

const disconnectAll = useCallback(async () => {
await Promise.allSettled([
disconnectAsync(),
isSolanaConnected ? disconnectSolana() : Promise.resolve(),
isStellarConnected && !isStellarExternalKit ? disconnectStellar() : Promise.resolve(),
]);
}, [disconnectAsync, isSolanaConnected, disconnectSolana, isStellarConnected, isStellarExternalKit, disconnectStellar]);


// Eagerly hydrate on mobile so deeplink wallets have a payId ready
// before the user taps "Pay with wallet".
const { hydrateOrder, order, paymentState: payState } = useRozoPay();
Expand Down Expand Up @@ -371,10 +380,11 @@ export default function SelectMethod() {
chain_type: "stellar",
});
}
await disconnectAsync();
await disconnectSolana();
if (!isStellarExternalKit) await disconnectStellar();
setRoute(isMobile ? ROUTES.MOBILECONNECTORS : ROUTES.CONNECTORS);
setUserDisconnected(true);
await disconnectAll();
setRoute(isMobile ? ROUTES.MOBILECONNECTORS : ROUTES.CONNECTORS, {
event: "click-select-another-method",
});
},
};
options.push(unconnectedWalletOption);
Expand All @@ -395,9 +405,8 @@ export default function SelectMethod() {
options.push({
...base,
disabled: depositAddressOptions.loading,
subtitle: depositAddressOptions.loading ? "Loading..." : undefined,
loading: depositAddressOptions.loading,
onClick: () => {
if (depositAddressOptions.loading) return;
capture(ROZO_EVENTS.PAYMENT_METHOD_SELECTED, {
field: "chain",
value: "deposit_address",
Expand All @@ -424,9 +433,8 @@ export default function SelectMethod() {
field: "chain",
value: "stellar",
});
await disconnectAsync();
await disconnectSolana();
if (!isStellarExternalKit) await disconnectStellar();
setUserDisconnected(true);
await disconnectAll();
setRoute(ROUTES.STELLAR_CONNECT);
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";

import {
ModalBody,
Expand All @@ -12,12 +12,14 @@ import { AnimatePresence, motion } from "framer-motion";
import { ROUTES } from "../../../../constants/routes";
import { usePayContext } from "../../../../hooks/usePayContext";
import styled from "../../../../styles/styled";
import Button from "../../../Common/Button";
import { walletConfigs } from "../../../../wallets/walletConfigs";
import SquircleSpinner from "../../../Spinners/SquircleSpinner";

const ConnectSolana: React.FC = () => {
const solanaWallets = useWallet();
const isConnected = solanaWallets.connected;
const [connectionError, setConnectionError] = useState<string | null>(null);

const { solanaConnector, setRoute, paymentState, routeMeta } =
usePayContext();
Expand Down Expand Up @@ -54,6 +56,20 @@ const ConnectSolana: React.FC = () => {
);
};

// Listen for adapter-level errors (e.g. WalletConnect proposal expired)
useEffect(() => {
const adapter = solanaWallets.wallet?.adapter;
if (!adapter) return;
const handleError = (error: unknown) => {
const msg = error instanceof Error ? error.message : String(error);
setConnectionError(msg || "Connection failed");
};
adapter.on("error", handleError);
return () => {
adapter.off("error", handleError);
};
}, [solanaWallets.wallet]);

useEffect(() => {
if (!solanaConnector) return;
solanaWallets.select(solanaConnector);
Expand Down Expand Up @@ -89,13 +105,41 @@ const ConnectSolana: React.FC = () => {
{getWalletIcon()}
</div>
}
loading={true}
loading={!isConnected && !connectionError}
/>
</AnimatePresence>
</AnimationContainer>
</LoadingContainer>
<ModalContent style={{ paddingBottom: 0 }}>
{isConnected ? (
{connectionError ? (
<>
<ModalH1>Connection Failed</ModalH1>
<ModalBody>{connectionError}</ModalBody>
<ButtonRow>
<Button
variant="primary"
onClick={() => {
setConnectionError(null);
solanaWallets.disconnect().then(() => {
solanaWallets.select(solanaConnector);
});
}}
>
Try Again
</Button>
<Button
variant="tertiary"
onClick={() =>
setRoute(ROUTES.SELECT_METHOD, {
event: "click-select-another-method",
})
}
>
Cancel
</Button>
</ButtonRow>
</>
) : isConnected ? (
<ModalH1>Connected</ModalH1>
) : (
<>
Expand Down Expand Up @@ -130,4 +174,11 @@ const AnimationContainer = styled(motion.div)`
}
`;

const ButtonRow = styled.div`
display: flex;
gap: 8px;
margin-top: 16px;
justify-content: center;
`;

export default ConnectSolana;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ROUTES } from "../../../../constants/routes";
import { usePayContext } from "../../../../hooks/usePayContext";
import { useStellar } from "../../../../provider/StellarContextProvider";
import styled from "../../../../styles/styled";
import Button from "../../../Common/Button";
import SquircleSpinner from "../../../Spinners/SquircleSpinner";

const ConnectorStellar: React.FC = () => {
Expand Down Expand Up @@ -104,6 +105,27 @@ const ConnectorStellar: React.FC = () => {
<>
<ModalH1>Connection Failed</ModalH1>
<ModalBody>{connectionError}</ModalBody>
<ButtonRow>
<Button
variant="primary"
onClick={() => {
setConnectionError(null);
setConnectionInitiated(false);
}}
>
Try Again
</Button>
<Button
variant="tertiary"
onClick={() =>
setRoute(ROUTES.SELECT_METHOD, {
event: "click-select-another-method",
})
}
>
Cancel
</Button>
</ButtonRow>
</>
) : isConnected ? (
<ModalH1>Connected</ModalH1>
Expand Down Expand Up @@ -138,4 +160,11 @@ const AnimationContainer = styled(motion.div)`
}
`;

const ButtonRow = styled.div`
display: flex;
gap: 8px;
margin-top: 16px;
justify-content: center;
`;

export default ConnectorStellar;
Loading
Loading