Skip to content

Commit ae3c88c

Browse files
feat: add preferredTokenAddress to wallet options
1 parent 9f779ad commit ae3c88c

File tree

23 files changed

+621
-268
lines changed

23 files changed

+621
-268
lines changed

examples/nextjs-app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"@farcaster/frame-sdk": "^0.0.26",
1414
"@headlessui/react": "^2.2.0",
1515
"@rainbow-me/rainbowkit": "^2.2.8",
16-
"@rozoai/intent-common": "workspace:*",
17-
"@rozoai/intent-pay": "workspace:*",
16+
"@rozoai/intent-common": "0.1.7-beta.1",
17+
"@rozoai/intent-pay": "0.1.7-beta.1",
1818
"@tanstack/react-query": "^5.51.11",
1919
"@types/react-syntax-highlighter": "^15.5.13",
2020
"@wagmi/core": "^2.22.0",

examples/nextjs-app/src/app/basic/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,8 @@ export default function DemoBasic() {
311311
console.log("✓ Payout completed:", e);
312312
}}
313313
feeType={FeeType.ExactOut}
314-
resetOnSuccess={true}
315314
metadata={metadata}
315+
resetOnSuccess
316316
showProcessingPayout
317317
>
318318
{(renderProps) => (

packages/connectkit/bundle-analysis.html

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

packages/connectkit/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@rozoai/intent-pay",
33
"private": false,
4-
"version": "0.1.5",
4+
"version": "0.1.7-beta.1",
55
"author": "RozoAI",
66
"homepage": "https://github.com/RozoAI/intent-pay",
77
"license": "BSD-2-Clause",
@@ -47,7 +47,7 @@
4747
"@albedo-link/intent": "^0.13.0",
4848
"@reown/appkit": "^1.7.0",
4949
"@rollup/plugin-typescript": "^12.1.2",
50-
"@rozoai/intent-common": "workspace:*",
50+
"@rozoai/intent-common": "0.1.7-beta.1",
5151
"@solana/spl-token": "^0.4.14",
5252
"@solana/wallet-adapter-base": "^0.9.27",
5353
"@solana/wallet-adapter-react": "^0.15.39",

packages/connectkit/src/components/Common/OrderHeader/index.tsx

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useAccount } from "wagmi";
66
import {
77
Base,
88
BinanceSmartChain,
9+
chainToLogo,
910
Ethereum,
1011
Polygon,
1112
Solana,
@@ -17,6 +18,7 @@ import { useRozoPay } from "../../../hooks/useDaimoPay";
1718
import { usePayContext } from "../../../hooks/usePayContext";
1819
import { useStellar } from "../../../provider/StellarContextProvider";
1920
import styled from "../../../styles/styled";
21+
import { EVM_CHAIN_IDS, NON_EVM_CHAIN_IDS } from "../../../types/chainAddress";
2022
import { formatUsd } from "../../../utils/format";
2123

2224
/** Shows payment amount. */
@@ -54,6 +56,31 @@ export const OrderHeader = ({
5456
);
5557
const orderUsd = order?.destFinalCallTokenAmount.usd;
5658
const appId = paymentState.payParams?.appId;
59+
const selectedChainId = paymentState.selectedChainId;
60+
61+
// Get chain logo component based on chainId
62+
const getChainLogo = (
63+
chainId: number | undefined
64+
): React.ReactNode | null => {
65+
if (!chainId) return null;
66+
67+
switch (chainId) {
68+
case EVM_CHAIN_IDS.BASE:
69+
return <Base />;
70+
case EVM_CHAIN_IDS.ETHEREUM:
71+
return <Ethereum />;
72+
case EVM_CHAIN_IDS.POLYGON:
73+
return <Polygon />;
74+
case 56: // BSC
75+
return <BinanceSmartChain />;
76+
case NON_EVM_CHAIN_IDS.SOLANA:
77+
return <Solana />;
78+
case NON_EVM_CHAIN_IDS.STELLAR:
79+
return <Stellar />;
80+
default:
81+
return null;
82+
}
83+
};
5784

5885
const titleAmountContent = (() => {
5986
if (paymentState.isDepositFlow) {
@@ -79,29 +106,40 @@ export const OrderHeader = ({
79106
return null;
80107
}
81108

109+
const chainLogo = selectedChainId ? chainToLogo[selectedChainId] : null;
110+
82111
return (
83-
<LogoContainer $size={size} $zIndex={1} style={{ borderRadius: "22.5%" }}>
84-
{typeof icon === "string" ? (
85-
<img
86-
src={icon}
87-
alt={name || "wallet"}
88-
style={{ maxWidth: "100%", maxHeight: "100%" }}
89-
/>
90-
) : (
91-
icon
92-
)}
93-
</LogoContainer>
112+
<WalletIconWrapper>
113+
<LogoContainer
114+
$size={size}
115+
$zIndex={1}
116+
style={{ borderRadius: "22.5%" }}
117+
>
118+
{typeof icon === "string" ? (
119+
<img
120+
src={icon}
121+
alt={name || "wallet"}
122+
style={{ maxWidth: "100%", maxHeight: "100%" }}
123+
/>
124+
) : (
125+
icon
126+
)}
127+
</LogoContainer>
128+
{chainLogo && <ChainLogoBadge>{chainLogo}</ChainLogoBadge>}
129+
</WalletIconWrapper>
94130
);
95131
};
96132

97-
const walletIcon = renderIcon(connector?.icon);
133+
const walletIcon = renderIcon(connector?.icon, undefined, 32);
98134
const solanaIcon = renderIcon(
99135
solanaWallet?.adapter.icon || <Solana />,
100-
solanaWallet?.adapter.name
136+
solanaWallet?.adapter.name,
137+
32
101138
);
102139
const stellarIcon = renderIcon(
103140
stellarConnector?.icon || <Stellar />,
104-
stellarConnector?.name
141+
stellarConnector?.name,
142+
32
105143
);
106144

107145
if (minified) {
@@ -307,3 +345,30 @@ const SubtitleContainer = styled.div`
307345
justify-content: flex-end;
308346
gap: 8px;
309347
`;
348+
349+
const WalletIconWrapper = styled.div`
350+
position: relative;
351+
display: inline-block;
352+
`;
353+
354+
const ChainLogoBadge = styled(motion.div)`
355+
position: absolute;
356+
bottom: -5px;
357+
right: 0px;
358+
display: flex;
359+
align-items: center;
360+
justify-content: center;
361+
width: 15px;
362+
height: 15px;
363+
overflow: hidden;
364+
z-index: 10;
365+
svg,
366+
img {
367+
display: block;
368+
position: relative;
369+
pointer-events: none;
370+
overflow: hidden;
371+
width: 100%;
372+
height: 100%;
373+
}
374+
`;

packages/connectkit/src/components/DaimoPayButton/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
RozoPayHydratedOrderWithOrg,
1515
rozoSolana,
1616
rozoStellar,
17+
TokenSymbol,
1718
writeRozoPayOrderID,
1819
} from "@rozoai/intent-common";
1920
import { AnimatePresence, Variants } from "framer-motion";
@@ -90,6 +91,7 @@ function RozoPayButtonCustom(props: RozoPayButtonCustomProps): JSX.Element {
9091
paymentOptions,
9192
preferredChains,
9293
preferredTokens,
94+
preferredSymbol,
9395
feeType,
9496
externalId,
9597
metadata,
@@ -106,6 +108,10 @@ function RozoPayButtonCustom(props: RozoPayButtonCustomProps): JSX.Element {
106108
paymentOptions,
107109
preferredChains,
108110
preferredTokens,
111+
preferredSymbol: preferredSymbol ?? [
112+
TokenSymbol.USDC,
113+
TokenSymbol.USDT,
114+
], // Default to USDC and USDT
109115
evmChains: undefined,
110116
externalId,
111117
metadata,

packages/connectkit/src/components/DaimoPayButton/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@rozoai/intent-common";
99
import { ReactElement } from "react";
1010
import { Address } from "viem";
11+
import { PreferredTokenSymbol } from "../../payment/paymentFsm";
1112
import { CustomTheme, Mode, Theme } from "../../types";
1213

1314
/** Chain-specific props for EVM chains (Base, Ethereum, Polygon) */
@@ -92,6 +93,11 @@ type CommonPaymentProps = {
9293
* Preferred tokens. These appear first in the token list.
9394
*/
9495
preferredTokens?: { chain: number; address: Address | string }[];
96+
/**
97+
* Preferred token symbols to filter. Only tokens with these symbols will be shown.
98+
* Default: [TokenSymbol.USDC, TokenSymbol.USDT]
99+
*/
100+
preferredSymbol?: PreferredTokenSymbol[];
95101
/**
96102
* Only allow payments on these EVM chains.
97103
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export default function ErrorPage() {
104104
};
105105
default:
106106
return {
107-
title: "Payment Error",
107+
title: "Payment Unavailable",
108108
message: errorMsg,
109109
canRetry: true,
110110
showSupport: true,

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

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getChainById, TokenSymbol } from "@rozoai/intent-common";
12
import { useWallet } from "@solana/wallet-adapter-react";
23
import { useMemo, useState } from "react";
34
import { injected, useAccount } from "wagmi";
@@ -10,7 +11,13 @@ import { usePayContext } from "../../../hooks/usePayContext";
1011
import { useTokenOptions } from "../../../hooks/useTokenOptions";
1112
import { useStellar } from "../../../provider/StellarContextProvider";
1213
import Button from "../../Common/Button";
13-
import { ModalContent, ModalH1, PageContent } from "../../Common/Modal/styles";
14+
import { OrDivider } from "../../Common/Modal";
15+
import {
16+
ModalBody,
17+
ModalContent,
18+
ModalH1,
19+
PageContent,
20+
} from "../../Common/Modal/styles";
1421
import { OptionsList } from "../../Common/OptionsList";
1522
import { OrderHeader } from "../../Common/OrderHeader";
1623
import SelectAnotherMethodButton from "../../Common/SelectAnotherMethodButton";
@@ -96,7 +103,7 @@ export default function SelectToken() {
96103
]);
97104

98105
// Prevent showing "Insufficient balance" too quickly to avoid flickering
99-
const showInsufficientBalance = useMemo(() => {
106+
const isEmptyOptionsList = useMemo(() => {
100107
return !isLoading && isConnected && optionsList.length === 0;
101108
}, [isLoading, isConnected, optionsList.length]);
102109

@@ -119,13 +126,22 @@ export default function SelectToken() {
119126
hideBottomLine={!isAnotherMethodButtonVisible}
120127
/>
121128
)}
122-
{showInsufficientBalance && !noConnectedWallet && (
123-
<InsufficientBalance onRefresh={refreshOptions} />
129+
{isEmptyOptionsList && !noConnectedWallet && (
130+
<NoTokensAvailable
131+
onRefresh={refreshOptions}
132+
chainId={paymentState.selectedChainId}
133+
preferredSymbol={paymentState.payParams?.preferredSymbol}
134+
/>
124135
)}
125136
{!isLoading && !isConnected && effectiveTokenMode === "all" && (
126137
<ConnectButton />
127138
)}
128-
{isAnotherMethodButtonVisible && <SelectAnotherMethodButton />}
139+
{isAnotherMethodButtonVisible && (
140+
<>
141+
{isEmptyOptionsList && <OrDivider />}
142+
<SelectAnotherMethodButton />
143+
</>
144+
)}
129145
{noConnectedWallet && <NoConnectedWallet />}
130146
</PageContent>
131147
);
@@ -140,10 +156,14 @@ function NoConnectedWallet() {
140156
);
141157
}
142158

143-
function InsufficientBalance({
159+
function NoTokensAvailable({
144160
onRefresh,
161+
preferredSymbol,
162+
chainId,
145163
}: {
146164
onRefresh: () => Promise<void>;
165+
chainId: number | undefined;
166+
preferredSymbol?: string[];
147167
}) {
148168
const [isRefreshing, setIsRefreshing] = useState(false);
149169

@@ -152,25 +172,70 @@ function InsufficientBalance({
152172
try {
153173
await onRefresh();
154174
} catch (error) {
155-
console.error("Failed to refresh balances:", error);
175+
console.error("Failed to refresh tokens:", error);
156176
} finally {
157177
setIsRefreshing(false);
158178
}
159179
};
160180

181+
// Get supported token symbols from payParams, default to USDC and USDT
182+
const supportedSymbols = useMemo(() => {
183+
return preferredSymbol ?? [TokenSymbol.USDC, TokenSymbol.USDT];
184+
}, [preferredSymbol]);
185+
186+
// Format token symbols for display
187+
const formattedTokens = useMemo(() => {
188+
if (supportedSymbols.length === 0) {
189+
return "supported tokens";
190+
}
191+
if (supportedSymbols.length === 1) {
192+
return <strong>{supportedSymbols[0]}</strong>;
193+
}
194+
if (supportedSymbols.length === 2) {
195+
return (
196+
<>
197+
<strong>{supportedSymbols[0]}</strong> and{" "}
198+
<strong>{supportedSymbols[1]}</strong>
199+
</>
200+
);
201+
}
202+
// For 3+ tokens, show first two and "others"
203+
const firstTwo = supportedSymbols.slice(0, 2);
204+
const remaining = supportedSymbols.length - 2;
205+
return (
206+
<>
207+
<strong>{firstTwo[0]}</strong>, <strong>{firstTwo[1]}</strong>
208+
{remaining > 0 && `, and ${remaining} other${remaining > 1 ? "s" : ""}`}
209+
</>
210+
);
211+
}, [supportedSymbols]);
212+
213+
const selectedChain = useMemo(() => {
214+
return chainId ? getChainById(chainId) : undefined;
215+
}, [chainId]);
216+
161217
return (
162218
<ModalContent
163219
style={{
164220
display: "flex",
165221
flexDirection: "column",
166222
alignItems: "center",
167223
justifyContent: "center",
168-
paddingTop: 16,
169-
paddingBottom: 16,
170-
gap: 16,
224+
paddingTop: 24,
225+
paddingBottom: 0,
226+
gap: 20,
171227
}}
172228
>
173-
<ModalH1>Insufficient balance</ModalH1>
229+
<ModalH1>No tokens available</ModalH1>
230+
<ModalBody
231+
style={{
232+
margin: "0 auto",
233+
padding: "0 12px",
234+
}}
235+
>
236+
We can&apos;t find any supported tokens ({formattedTokens}) in your
237+
account.
238+
</ModalBody>
174239
<Button
175240
variant="secondary"
176241
onClick={handleRefresh}

0 commit comments

Comments
 (0)