@b3dotfun/sdk 0.0.30-alpha.9 → 0.0.31-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
  2. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +6 -1
  3. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  4. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
  5. package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +3 -1
  6. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +3 -3
  7. package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
  8. package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.js +94 -0
  9. package/dist/cjs/global-account/react/components/ManageAccount/ContentTokens.js +5 -0
  10. package/dist/cjs/global-account/react/components/ManageAccount/ManageAccount.js +3 -40
  11. package/dist/cjs/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
  12. package/dist/cjs/global-account/react/components/ManageAccount/TokenBalanceRow.js +8 -0
  13. package/dist/cjs/global-account/react/components/TokenIcon.d.ts +11 -0
  14. package/dist/cjs/global-account/react/components/TokenIcon.js +43 -0
  15. package/dist/cjs/global-account/react/components/ui/accordion.d.ts +7 -0
  16. package/dist/cjs/global-account/react/components/ui/accordion.js +53 -0
  17. package/dist/cjs/global-account/react/components/ui/dialog.js +1 -1
  18. package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +2 -0
  19. package/dist/cjs/global-account/utils/analytics.d.ts +6 -0
  20. package/dist/cjs/global-account/utils/analytics.js +1 -0
  21. package/dist/esm/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
  22. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +5 -1
  23. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  24. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
  25. package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +3 -1
  26. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +4 -4
  27. package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
  28. package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.js +88 -0
  29. package/dist/esm/global-account/react/components/ManageAccount/ContentTokens.js +2 -0
  30. package/dist/esm/global-account/react/components/ManageAccount/ManageAccount.js +6 -40
  31. package/dist/esm/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
  32. package/dist/esm/global-account/react/components/ManageAccount/TokenBalanceRow.js +5 -0
  33. package/dist/esm/global-account/react/components/TokenIcon.d.ts +11 -0
  34. package/dist/esm/global-account/react/components/TokenIcon.js +37 -0
  35. package/dist/esm/global-account/react/components/ui/accordion.d.ts +7 -0
  36. package/dist/esm/global-account/react/components/ui/accordion.js +14 -0
  37. package/dist/esm/global-account/react/components/ui/dialog.js +1 -1
  38. package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +2 -0
  39. package/dist/esm/global-account/utils/analytics.d.ts +6 -0
  40. package/dist/esm/global-account/utils/analytics.js +1 -0
  41. package/dist/styles/index.css +1 -1
  42. package/dist/types/anyspend/react/components/AnyspendDepositHype.d.ts +4 -0
  43. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
  44. package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +3 -1
  45. package/dist/types/global-account/react/components/ManageAccount/BalanceContent.d.ts +6 -0
  46. package/dist/types/global-account/react/components/ManageAccount/TokenBalanceRow.d.ts +10 -0
  47. package/dist/types/global-account/react/components/TokenIcon.d.ts +11 -0
  48. package/dist/types/global-account/react/components/ui/accordion.d.ts +7 -0
  49. package/dist/types/global-account/utils/analytics.d.ts +6 -0
  50. package/package.json +10 -18
  51. package/src/anyspend/react/components/AnySpendStakeB3.tsx +1 -1
  52. package/src/anyspend/react/components/AnyspendDepositHype.tsx +9 -0
  53. package/src/anyspend/react/components/AnyspendSignatureMint.tsx +4 -4
  54. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +12 -3
  55. package/src/anyspend/react/components/common/PanelOnramp.tsx +8 -4
  56. package/src/global-account/react/components/ManageAccount/BalanceContent.tsx +228 -0
  57. package/src/global-account/react/components/ManageAccount/ContentTokens.tsx +3 -0
  58. package/src/global-account/react/components/ManageAccount/ManageAccount.tsx +7 -285
  59. package/src/global-account/react/components/ManageAccount/TokenBalanceRow.tsx +46 -0
  60. package/src/global-account/react/components/TokenIcon.tsx +87 -0
  61. package/src/global-account/react/components/ui/accordion.tsx +53 -0
  62. package/src/global-account/react/components/ui/dialog.tsx +1 -1
  63. package/src/global-account/react/hooks/useB3BalanceFromAddresses.ts +1 -1
  64. package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +3 -0
  65. package/src/global-account/utils/analytics.ts +10 -0
  66. package/dist/cjs/index.d.ts +0 -0
  67. package/dist/cjs/index.js +0 -2
  68. package/dist/esm/index.d.ts +0 -0
  69. package/dist/esm/index.js +0 -2
  70. package/dist/types/index.d.ts +0 -0
  71. package/src/index.ts +0 -1
@@ -18,15 +18,15 @@ function generateEncodedDataForSignatureMint(signatureData: GenerateSigMintRespo
18
18
  invariant(payload, "Payload is required");
19
19
 
20
20
  const mintRequest = {
21
- to: payload.to,
22
- royaltyRecipient: payload.royaltyRecipient,
21
+ to: payload.to as `0x${string}`,
22
+ royaltyRecipient: payload.royaltyRecipient as `0x${string}`,
23
23
  royaltyBps: BigInt(payload.royaltyBps || 0),
24
- primarySaleRecipient: payload.primarySaleRecipient,
24
+ primarySaleRecipient: payload.primarySaleRecipient as `0x${string}`,
25
25
  tokenId: BigInt(payload.tokenId || 0),
26
26
  uri: payload.uri,
27
27
  quantity: BigInt(payload.quantity || 1),
28
28
  pricePerToken: parseEther(payload.price?.toString() || "0"),
29
- currency: payload.currencyAddress,
29
+ currency: payload.currencyAddress as `0x${string}`,
30
30
  validityStartTimestamp: BigInt(payload.mintStartTime || 0),
31
31
  validityEndTimestamp: BigInt(payload.mintEndTime || 0),
32
32
  uid: payload.uid as `0x${string}`,
@@ -24,6 +24,9 @@ interface CryptoReceiveSectionProps {
24
24
  onChangeDstAmount?: (value: string) => void;
25
25
  // Quote data
26
26
  anyspendQuote?: any;
27
+ // custom dst token data
28
+ dstTokenSymbol?: string;
29
+ dstTokenLogoURI?: string;
27
30
  }
28
31
 
29
32
  export function CryptoReceiveSection({
@@ -39,6 +42,8 @@ export function CryptoReceiveSection({
39
42
  setSelectedDstToken,
40
43
  onChangeDstAmount,
41
44
  anyspendQuote,
45
+ dstTokenSymbol,
46
+ dstTokenLogoURI,
42
47
  }: CryptoReceiveSectionProps) {
43
48
  return (
44
49
  <motion.div
@@ -72,10 +77,14 @@ export function CryptoReceiveSection({
72
77
  <div className="flex items-center justify-between">
73
78
  <div className="text-as-primary text-2xl font-bold">{dstAmount || "0"}</div>
74
79
  <div className="bg-as-brand/10 border-as-brand/30 flex items-center gap-3 rounded-xl border px-4 py-3">
75
- {dstToken.metadata?.logoURI && (
76
- <img src={dstToken.metadata.logoURI} alt={dstToken.symbol} className="h-8 w-8 rounded-full" />
80
+ {(dstTokenLogoURI || dstToken.metadata?.logoURI) && (
81
+ <img
82
+ src={dstTokenLogoURI || dstToken.metadata?.logoURI}
83
+ alt={dstTokenSymbol || dstToken.symbol}
84
+ className="h-8 w-8 rounded-full"
85
+ />
77
86
  )}
78
- <span className="text-as-brand text-lg font-bold">{dstToken.symbol}</span>
87
+ <span className="text-as-brand text-lg font-bold">{dstTokenSymbol || dstToken.symbol}</span>
79
88
  </div>
80
89
  </div>
81
90
  ) : (
@@ -2,7 +2,7 @@ import { useCoinbaseOnrampOptions, useGeoOnrampOptions } from "@b3dotfun/sdk/any
2
2
  import { components } from "@b3dotfun/sdk/anyspend/types/api";
3
3
  import { ALL_CHAINS } from "@b3dotfun/sdk/anyspend/utils/chain";
4
4
  import { Input, useGetGeo, useProfile } from "@b3dotfun/sdk/global-account/react";
5
- import { formatUsername } from "@b3dotfun/sdk/shared/utils";
5
+ import { cn, formatUsername } from "@b3dotfun/sdk/shared/utils";
6
6
  import { formatAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
7
7
  import { ChevronRight, Wallet } from "lucide-react";
8
8
  import { useRef } from "react";
@@ -23,6 +23,8 @@ export function PanelOnramp({
23
23
  onDestinationChainChange,
24
24
  fiatPaymentMethodIndex,
25
25
  recipientSelectionPanelIndex,
26
+ dstTokenSymbol,
27
+ hideDstToken = false,
26
28
  }: {
27
29
  srcAmountOnRamp: string;
28
30
  setSrcAmountOnRamp: (amount: string) => void;
@@ -36,6 +38,8 @@ export function PanelOnramp({
36
38
  onDestinationChainChange?: (chainId: number) => void;
37
39
  fiatPaymentMethodIndex: number;
38
40
  recipientSelectionPanelIndex: number;
41
+ dstTokenSymbol?: string;
42
+ hideDstToken?: boolean;
39
43
  }) {
40
44
  // Get geo-based onramp options to access fee information
41
45
  const { stripeWeb2Support } = useGeoOnrampOptions(srcAmountOnRamp);
@@ -163,7 +167,7 @@ export function PanelOnramp({
163
167
  </div>
164
168
 
165
169
  {/* Quick Amount Buttons */}
166
- <div className="mx-auto mb-6 inline-grid grid-cols-4 gap-2">
170
+ <div className={cn("mx-auto mb-6 inline-grid grid-cols-4 gap-2", hideDstToken && "mb-0")}>
167
171
  {["5", "10", "20", "25"].map(value => (
168
172
  <button
169
173
  key={value}
@@ -180,7 +184,7 @@ export function PanelOnramp({
180
184
  </div>
181
185
 
182
186
  {/* Token Display */}
183
- {destinationToken && destinationChainId && (
187
+ {destinationToken && destinationChainId && !hideDstToken && (
184
188
  <OrderTokenAmountFiat
185
189
  address={_recipientAddress}
186
190
  context="to"
@@ -226,7 +230,7 @@ export function PanelOnramp({
226
230
  <span className="text-as-tertiarry text-sm">Expected to receive</span>
227
231
  <div className="flex items-center gap-2">
228
232
  <span className="text-as-primary font-semibold">
229
- {destinationAmount || "0"} {destinationToken?.symbol || ""}
233
+ {destinationAmount || "0"} {dstTokenSymbol || destinationToken?.symbol || ""}
230
234
  </span>
231
235
  <span className="text-as-tertiarry text-sm">
232
236
  on {destinationChainId ? ALL_CHAINS[destinationChainId]?.name : ""}
@@ -0,0 +1,228 @@
1
+ import {
2
+ Button,
3
+ CopyToClipboard,
4
+ useAuthentication,
5
+ useB3BalanceFromAddresses,
6
+ useModalStore,
7
+ useNativeBalance,
8
+ useProfile,
9
+ } from "@b3dotfun/sdk/global-account/react";
10
+ import { BankIcon } from "@b3dotfun/sdk/global-account/react/components/icons/BankIcon";
11
+ import { SignOutIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SignOutIcon";
12
+ import { SwapIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SwapIcon";
13
+ import { formatUsername } from "@b3dotfun/sdk/shared/utils";
14
+ import { Loader2, Pencil } from "lucide-react";
15
+ import { useEffect, useRef, useState } from "react";
16
+ import { useActiveAccount } from "thirdweb/react";
17
+ import useFirstEOA from "../../hooks/useFirstEOA";
18
+ import { B3TokenIcon, EthereumTokenIcon } from "../TokenIcon";
19
+ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion";
20
+ import { TokenBalanceRow } from "./TokenBalanceRow";
21
+
22
+ interface BalanceContentProps {
23
+ onLogout?: () => void;
24
+ partnerId: string;
25
+ }
26
+
27
+ function centerTruncate(str: string, length = 4) {
28
+ if (str.length <= length * 2) return str;
29
+ return `${str.slice(0, length)}...${str.slice(-length)}`;
30
+ }
31
+
32
+ export function BalanceContent({ onLogout, partnerId }: BalanceContentProps) {
33
+ const account = useActiveAccount();
34
+ const { address: eoaAddress, info: eoaInfo } = useFirstEOA();
35
+ const { data: profile } = useProfile({
36
+ address: eoaAddress || account?.address,
37
+ fresh: true,
38
+ });
39
+ const { setB3ModalOpen, setB3ModalContentType } = useModalStore();
40
+ const { logout } = useAuthentication(partnerId);
41
+ const [logoutLoading, setLogoutLoading] = useState(false);
42
+ const [openAccordions, setOpenAccordions] = useState<string[]>([]);
43
+ const hasExpandedRef = useRef(false);
44
+
45
+ console.log("eoaAddress", eoaAddress);
46
+ console.log("account?.address", account?.address);
47
+
48
+ // Balance data fetching
49
+ const { data: eoaNativeBalance, isLoading: eoaNativeLoading } = useNativeBalance(eoaAddress);
50
+ const { data: eoaB3Balance, isLoading: eoaB3Loading } = useB3BalanceFromAddresses(eoaAddress);
51
+ const { data: b3Balance, isLoading: b3Loading } = useB3BalanceFromAddresses(account?.address);
52
+ const { data: nativeBalance, isLoading: nativeLoading } = useNativeBalance(account?.address);
53
+
54
+ // Calculate total USD values for comparison
55
+ const globalAccountTotalUsd = (b3Balance?.balanceUsd || 0) + (nativeBalance?.totalUsd || 0);
56
+ const eoaTotalUsd = (eoaB3Balance?.balanceUsd || 0) + (eoaNativeBalance?.totalUsd || 0);
57
+
58
+ // Check if both data sets are ready (not loading and have data)
59
+ const isGlobalDataReady = !b3Loading && !nativeLoading && b3Balance !== undefined && nativeBalance !== undefined;
60
+ const isEoaDataReady =
61
+ !eoaAddress || (!eoaB3Loading && !eoaNativeLoading && eoaB3Balance !== undefined && eoaNativeBalance !== undefined);
62
+ const isBothDataReady = isGlobalDataReady && isEoaDataReady;
63
+
64
+ // Reset expansion flag when component mounts
65
+ useEffect(() => {
66
+ hasExpandedRef.current = false;
67
+ setOpenAccordions([]);
68
+ }, []);
69
+
70
+ // Auto-expand the appropriate section when data becomes ready
71
+ useEffect(() => {
72
+ if (isBothDataReady && !hasExpandedRef.current && eoaAddress && account?.address) {
73
+ hasExpandedRef.current = true;
74
+
75
+ // Determine which section to expand based on higher balance
76
+ if (globalAccountTotalUsd === 0 && eoaTotalUsd === 0) {
77
+ // If both have 0 balance, expand global account by default
78
+ setOpenAccordions(["global-account"]);
79
+ } else if (globalAccountTotalUsd >= eoaTotalUsd) {
80
+ setOpenAccordions(["global-account"]);
81
+ } else {
82
+ setOpenAccordions(["eoa-account"]);
83
+ }
84
+ }
85
+ }, [isBothDataReady, globalAccountTotalUsd, eoaTotalUsd, eoaAddress, account?.address]);
86
+
87
+ const onLogoutEnhanced = async () => {
88
+ setLogoutLoading(true);
89
+ await logout();
90
+ onLogout?.();
91
+ setB3ModalOpen(false);
92
+ setLogoutLoading(false);
93
+ };
94
+
95
+ return (
96
+ <div className="flex flex-col gap-6">
97
+ {/* Profile Section */}
98
+ <div className="flex items-center justify-between">
99
+ <div className="flex items-center gap-4">
100
+ <div className="relative">
101
+ {profile?.avatar ? (
102
+ <img src={profile?.avatar} alt="Profile" className="size-24 rounded-full" />
103
+ ) : (
104
+ <div className="bg-b3-primary-wash size-24 rounded-full" />
105
+ )}
106
+ <div className="bg-b3-grey border-b3-background absolute -bottom-1 -right-1 flex size-8 items-center justify-center rounded-full border-4">
107
+ <Pencil size={16} className="text-b3-background" />
108
+ </div>
109
+ </div>
110
+ <div>
111
+ <h2 className="text-b3-grey text-xl font-semibold">
112
+ {profile?.displayName || formatUsername(profile?.name || "")}
113
+ </h2>
114
+ <div className="border-b3-line bg-b3-line/20 hover:bg-b3-line/40 flex w-fit items-center gap-2 rounded-full border px-3 py-1 transition-colors">
115
+ <span className="text-b3-foreground-muted font-mono text-xs">
116
+ {centerTruncate(account?.address || "", 6)}
117
+ </span>
118
+ <CopyToClipboard text={account?.address || ""} />
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ {/* Quick Actions */}
125
+ <div className="grid grid-cols-2 gap-3">
126
+ <Button
127
+ className="manage-account-deposit bg-b3-primary-wash hover:bg-b3-primary-wash/70 h-[84px] w-full flex-col items-start gap-2 rounded-2xl"
128
+ onClick={() => {
129
+ setB3ModalOpen(true);
130
+ setB3ModalContentType({
131
+ type: "anySpend",
132
+ defaultActiveTab: "fiat",
133
+ showBackButton: true,
134
+ });
135
+ }}
136
+ >
137
+ <BankIcon size={24} className="text-b3-primary-blue shrink-0" />
138
+ <div className="text-b3-grey font-neue-montreal-semibold">Deposit</div>
139
+ </Button>
140
+ <Button
141
+ className="manage-account-swap bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex h-[84px] w-full flex-col items-start gap-2 rounded-2xl"
142
+ onClick={() => {
143
+ setB3ModalOpen(true);
144
+ setB3ModalContentType({
145
+ type: "anySpend",
146
+ showBackButton: true,
147
+ });
148
+ }}
149
+ >
150
+ <SwapIcon size={24} className="text-b3-primary-blue" />
151
+ <div className="text-b3-grey font-neue-montreal-semibold">Swap</div>
152
+ </Button>
153
+ </div>
154
+
155
+ {/* Balance Sections with Accordions */}
156
+ <Accordion type="multiple" value={openAccordions} onValueChange={setOpenAccordions} className="space-y-2">
157
+ {/* Global Account Balance Section */}
158
+ <AccordionItem value="global-account" className="border-none">
159
+ <AccordionTrigger className="text-b3-grey font-neue-montreal-semibold py-2 hover:no-underline">
160
+ <span>Balance</span>
161
+ </AccordionTrigger>
162
+ <AccordionContent className="space-y-4">
163
+ <TokenBalanceRow
164
+ icon={<B3TokenIcon className="size-10" />}
165
+ name="B3"
166
+ balance={`${b3Balance?.formattedTotal || "0.00"} B3`}
167
+ usdValue={b3Balance?.balanceUsdFormatted || "0.00"}
168
+ priceChange={b3Balance?.priceChange24h}
169
+ />
170
+ <TokenBalanceRow
171
+ icon={<EthereumTokenIcon className="size-10" />}
172
+ name="Ethereum"
173
+ balance={`${nativeBalance?.formattedTotal || "0.00"} ETH`}
174
+ usdValue={nativeBalance?.formattedTotalUsd || "0.00"}
175
+ priceChange={nativeBalance?.priceChange24h}
176
+ />
177
+ </AccordionContent>
178
+ </AccordionItem>
179
+
180
+ {/* EOA Account Balance Section */}
181
+ {eoaAddress && (
182
+ <AccordionItem value="eoa-account" className="border-none">
183
+ <AccordionTrigger className="text-b3-grey font-neue-montreal-semibold py-2 hover:no-underline">
184
+ <div className="flex items-center gap-3">
185
+ <span>Connected {eoaInfo?.data?.name || "Wallet"}</span>
186
+ <div className="border-b3-line bg-b3-line/20 hover:bg-b3-line/40 flex w-fit items-center gap-2 rounded-full border px-3 py-1 transition-colors">
187
+ <span className="text-b3-foreground-muted font-mono text-xs">{centerTruncate(eoaAddress, 6)}</span>
188
+ <CopyToClipboard text={eoaAddress} />
189
+ </div>
190
+ </div>
191
+ </AccordionTrigger>
192
+ <AccordionContent className="space-y-4">
193
+ <TokenBalanceRow
194
+ icon={<B3TokenIcon className="size-10" />}
195
+ name="B3"
196
+ balance={`${eoaB3Balance?.formattedTotal || "0.00"} B3`}
197
+ usdValue={eoaB3Balance?.balanceUsdFormatted || "0.00"}
198
+ priceChange={eoaB3Balance?.priceChange24h}
199
+ />
200
+ <TokenBalanceRow
201
+ icon={<EthereumTokenIcon className="size-10" />}
202
+ name="Ethereum"
203
+ balance={`${eoaNativeBalance?.formattedTotal || "0.00"} ETH`}
204
+ usdValue={eoaNativeBalance?.formattedTotalUsd || "0.00"}
205
+ priceChange={eoaNativeBalance?.priceChange24h}
206
+ />
207
+ </AccordionContent>
208
+ </AccordionItem>
209
+ )}
210
+ </Accordion>
211
+
212
+ {/* Sign Out */}
213
+ <button
214
+ className="border-b3-line hover:bg-b3-line relative flex w-full items-center justify-center rounded-2xl border p-4 transition-colors"
215
+ onClick={onLogoutEnhanced}
216
+ >
217
+ <span className="font-neue-montreal-semibold text-b3-grey">Sign out</span>
218
+ <div className="absolute right-4">
219
+ {logoutLoading ? (
220
+ <Loader2 className="animate-spin" size={16} />
221
+ ) : (
222
+ <SignOutIcon size={16} className="text-b3-grey" />
223
+ )}
224
+ </div>
225
+ </button>
226
+ </div>
227
+ );
228
+ }
@@ -15,6 +15,7 @@ import { toast } from "sonner";
15
15
  import { useActiveAccount } from "thirdweb/react";
16
16
  import { encodeFunctionData, erc20Abi, isAddress, parseUnits } from "viem";
17
17
  import { SimBalanceItem } from "../../hooks/useSimBalance";
18
+ import invariant from "invariant";
18
19
 
19
20
  // Panel view enum for managing navigation between token list and send form
20
21
  enum TokenPanelView {
@@ -172,6 +173,8 @@ export function ContentTokens({ activeTab }: ContentTokensProps) {
172
173
  };
173
174
 
174
175
  try {
176
+ invariant(isAddress(recipientAddress), "Recipient address is not a valid address");
177
+
175
178
  const sendTokenData = encodeFunctionData({
176
179
  abi: erc20Abi,
177
180
  functionName: "transfer",