@b3dotfun/sdk 0.0.40-alpha.3 → 0.0.40-alpha.5

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.
@@ -1,11 +1,13 @@
1
1
  import app from "@b3dotfun/sdk/global-account/app";
2
2
  import { ecosystemWalletId } from "@b3dotfun/sdk/shared/constants";
3
+ import { thirdwebB3Mainnet } from "@b3dotfun/sdk/shared/constants/chains/b3Chain";
3
4
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
4
- import { Loader2, Mail, Phone } from "lucide-react";
5
+ import { Loader2, Mail, Phone, WalletIcon } from "lucide-react";
5
6
  import { useCallback, useEffect, useState } from "react";
6
7
  import { toast } from "sonner";
7
8
  import { useLinkProfile, useProfiles } from "thirdweb/react";
8
- import { preAuthenticate } from "thirdweb/wallets";
9
+ import { createWallet, preAuthenticate, WalletId } from "thirdweb/wallets";
10
+ import { WalletRow } from "../..";
9
11
  import { LinkAccountModalProps, useModalStore } from "../../stores/useModalStore";
10
12
  import { getProfileDisplayInfo } from "../../utils/profileDisplay";
11
13
  import { useB3 } from "../B3Provider/useB3";
@@ -17,13 +19,14 @@ import { XIcon } from "../icons/XIcon";
17
19
  import { Button } from "../ui/button";
18
20
  type OTPStrategy = "email" | "phone";
19
21
  type SocialStrategy = "google" | "x" | "discord" | "apple" | "farcaster";
20
- type Strategy = OTPStrategy | SocialStrategy;
22
+ type Strategy = OTPStrategy | SocialStrategy | "wallet";
21
23
 
22
24
  interface AuthMethod {
23
25
  id: Strategy;
24
26
  label: string;
25
27
  enabled: boolean;
26
28
  icon: React.ReactNode;
29
+ walletType?: WalletId;
27
30
  }
28
31
 
29
32
  const AUTH_METHODS: AuthMethod[] = [
@@ -41,6 +44,39 @@ const AUTH_METHODS: AuthMethod[] = [
41
44
  },
42
45
  ];
43
46
 
47
+ const WALLET_METHODS: AuthMethod[] = [
48
+ {
49
+ id: "wallet",
50
+ label: "Wallet",
51
+ enabled: true,
52
+ icon: <WalletIcon className="size-6" />,
53
+ walletType: "com.coinbase.wallet",
54
+ },
55
+ { id: "wallet", label: "Wallet", enabled: true, icon: <WalletIcon className="size-6" />, walletType: "io.metamask" },
56
+ {
57
+ id: "wallet",
58
+ label: "Wallet",
59
+ enabled: true,
60
+ icon: <WalletIcon className="size-6" />,
61
+ walletType: "me.rainbow",
62
+ },
63
+ {
64
+ id: "wallet",
65
+ label: "Wallet",
66
+ enabled: true,
67
+ icon: <WalletIcon className="size-6" />,
68
+ walletType: "app.phantom",
69
+ },
70
+ { id: "wallet", label: "Wallet", enabled: true, icon: <WalletIcon className="size-6" />, walletType: "io.rabby" },
71
+ {
72
+ id: "wallet",
73
+ label: "Wallet",
74
+ enabled: true,
75
+ icon: <WalletIcon className="size-6" />,
76
+ walletType: "walletConnect",
77
+ },
78
+ ];
79
+
44
80
  export function LinkAccount({
45
81
  onSuccess: onSuccessCallback,
46
82
  onError,
@@ -60,7 +96,7 @@ export function LinkAccount({
60
96
 
61
97
  // Get connected auth methods
62
98
  const connectedAuthMethods = profilesRaw
63
- .filter((profile: any) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
99
+ .filter((profile: any) => !["custom_auth_endpoint"].includes(profile.type))
64
100
  .map((profile: any) => profile.type as Strategy);
65
101
 
66
102
  // Filter available auth methods
@@ -69,7 +105,7 @@ export function LinkAccount({
69
105
  );
70
106
 
71
107
  const profiles = profilesRaw
72
- .filter((profile: any) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
108
+ .filter((profile: any) => !["custom_auth_endpoint"].includes(profile.type))
73
109
  .map((profile: any) => ({
74
110
  ...getProfileDisplayInfo(profile),
75
111
  originalProfile: profile,
@@ -211,6 +247,29 @@ export function LinkAccount({
211
247
  }
212
248
  };
213
249
 
250
+ const handleLinkWallet = async (walletType: WalletId) => {
251
+ setLinkingState(true, "wallet");
252
+ console.log("selectedMethod", walletType);
253
+ try {
254
+ if (!walletType) {
255
+ throw new Error("Wallet type not found");
256
+ }
257
+ await linkProfile(
258
+ {
259
+ client,
260
+ strategy: "wallet",
261
+ wallet: createWallet(walletType),
262
+ chain: thirdwebB3Mainnet,
263
+ },
264
+ mutationOptions,
265
+ );
266
+ } catch (error) {
267
+ console.error("Error linking account:", error);
268
+ setError(error instanceof Error ? error.message : "Failed to link account");
269
+ onError?.(error as Error);
270
+ }
271
+ };
272
+
214
273
  const handleSocialLink = async (strategy: SocialStrategy) => {
215
274
  try {
216
275
  console.log("handleSocialLink", strategy);
@@ -338,6 +397,20 @@ export function LinkAccount({
338
397
  )}
339
398
  </Button>
340
399
  ))}
400
+ {WALLET_METHODS.map(method => {
401
+ if (!method.walletType) {
402
+ return null;
403
+ }
404
+
405
+ return (
406
+ <WalletRow
407
+ key={method.walletType}
408
+ walletId={method.walletType as WalletId}
409
+ onClick={() => handleLinkWallet(method.walletType as WalletId)}
410
+ isLoading={isLinking}
411
+ />
412
+ );
413
+ })}
341
414
  {availableAuthMethods.length === 0 && (
342
415
  <div className="text-b3-foreground-muted py-8 text-center">
343
416
  All available authentication methods have been connected
@@ -379,38 +452,39 @@ export function LinkAccount({
379
452
 
380
453
  {error && <div className="text-b3-negative font-neue-montreal-medium py-2 text-sm">{error}</div>}
381
454
 
382
- {otpSent ? (
383
- <div className="space-y-4">
384
- <div className="space-y-2">
385
- <label className="text-b3-grey font-neue-montreal-medium text-sm">Verification Code</label>
386
- <input
387
- type="text"
388
- placeholder="Enter verification code"
389
- className="bg-b3-line text-b3-grey font-neue-montreal-medium focus:ring-b3-primary-blue/20 w-full rounded-xl p-4 focus:outline-none focus:ring-2"
390
- value={otp}
391
- onChange={e => setOtp(e.target.value)}
392
- />
455
+ {(selectedMethod === "email" || selectedMethod === "phone") &&
456
+ (otpSent ? (
457
+ <div className="space-y-4">
458
+ <div className="space-y-2">
459
+ <label className="text-b3-grey font-neue-montreal-medium text-sm">Verification Code</label>
460
+ <input
461
+ type="text"
462
+ placeholder="Enter verification code"
463
+ className="bg-b3-line text-b3-grey font-neue-montreal-medium focus:ring-b3-primary-blue/20 w-full rounded-xl p-4 focus:outline-none focus:ring-2"
464
+ value={otp}
465
+ onChange={e => setOtp(e.target.value)}
466
+ />
467
+ </div>
468
+ <Button
469
+ className="bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold h-12 w-full text-white"
470
+ onClick={handleLinkAccount}
471
+ >
472
+ Link Account
473
+ </Button>
393
474
  </div>
475
+ ) : (
394
476
  <Button
395
477
  className="bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold h-12 w-full text-white"
396
- onClick={handleLinkAccount}
478
+ onClick={handleSendOTP}
479
+ disabled={(!email && !phone) || (isLinking && linkingMethod === selectedMethod)}
397
480
  >
398
- Link Account
481
+ {isLinking && linkingMethod === selectedMethod ? (
482
+ <Loader2 className="animate-spin" />
483
+ ) : (
484
+ "Send Verification Code"
485
+ )}
399
486
  </Button>
400
- </div>
401
- ) : (
402
- <Button
403
- className="bg-b3-primary-blue hover:bg-b3-primary-blue/90 font-neue-montreal-semibold h-12 w-full text-white"
404
- onClick={handleSendOTP}
405
- disabled={(!email && !phone) || (isLinking && linkingMethod === selectedMethod)}
406
- >
407
- {isLinking && linkingMethod === selectedMethod ? (
408
- <Loader2 className="animate-spin" />
409
- ) : (
410
- "Send Verification Code"
411
- )}
412
- </Button>
413
- )}
487
+ ))}
414
488
  </div>
415
489
  )}
416
490
  </div>
@@ -18,6 +18,7 @@ import {
18
18
  import { SignOutIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SignOutIcon";
19
19
  import { formatNumber } from "@b3dotfun/sdk/shared/utils/formatNumber";
20
20
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
21
+ import { truncateAddress } from "@b3dotfun/sdk/shared/utils/truncateAddress";
21
22
  import { BarChart3, Coins, Copy, Image, LinkIcon, Loader2, Pencil, Settings, UnlinkIcon } from "lucide-react";
22
23
  import { useRef, useState } from "react";
23
24
  import { toast } from "sonner";
@@ -29,6 +30,24 @@ import { getProfileDisplayInfo } from "../../utils/profileDisplay";
29
30
  import { AccountAssets } from "../AccountAssets/AccountAssets";
30
31
  import { ContentTokens } from "./ContentTokens";
31
32
 
33
+ // Helper function to check if a string is a wallet address and format it
34
+ const formatProfileTitle = (title: string): { displayTitle: string; isAddress: boolean } => {
35
+ // Check if title looks like an Ethereum address (0x followed by 40 hex characters)
36
+ const isEthereumAddress = /^0x[a-fA-F0-9]{40}$/.test(title);
37
+
38
+ if (isEthereumAddress) {
39
+ return {
40
+ displayTitle: truncateAddress(title),
41
+ isAddress: true,
42
+ };
43
+ }
44
+
45
+ return {
46
+ displayTitle: title,
47
+ isAddress: false,
48
+ };
49
+ };
50
+
32
51
  import { Referrals, Users } from "@b3dotfun/b3-api";
33
52
  import { BalanceContent } from "./BalanceContent";
34
53
 
@@ -200,7 +219,7 @@ export function ManageAccount({
200
219
  };
201
220
 
202
221
  const profiles = profilesRaw
203
- .filter((profile: any) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
222
+ .filter((profile: any) => !["custom_auth_endpoint"].includes(profile.type))
204
223
  .map((profile: any) => ({
205
224
  ...getProfileDisplayInfo(profile),
206
225
  originalProfile: profile,
@@ -236,6 +255,8 @@ export function ManageAccount({
236
255
  });
237
256
  };
238
257
 
258
+ console.log("@@profiles", profiles);
259
+
239
260
  return (
240
261
  <div className="linked-accounts-settings space-y-8">
241
262
  {/* Linked Accounts Section */}
@@ -269,7 +290,7 @@ export function ManageAccount({
269
290
  {profiles.map(profile => (
270
291
  <div
271
292
  key={profile.title}
272
- className="linked-account-item bg-b3-line flex items-center justify-between rounded-xl p-4"
293
+ className="linked-account-item bg-b3-line group flex items-center justify-between rounded-xl p-4"
273
294
  >
274
295
  <div className="linked-account-info flex items-center gap-3">
275
296
  {profile.imageUrl ? (
@@ -287,9 +308,43 @@ export function ManageAccount({
287
308
  )}
288
309
  <div className="linked-account-details">
289
310
  <div className="linked-account-title-row flex items-center gap-2">
290
- <span className="linked-account-title text-b3-grey font-neue-montreal-semibold">
291
- {profile.title}
292
- </span>
311
+ {(() => {
312
+ const { displayTitle, isAddress } = formatProfileTitle(profile.title);
313
+
314
+ const handleCopyAddress = async (e: React.MouseEvent) => {
315
+ e.stopPropagation();
316
+ try {
317
+ await navigator.clipboard.writeText(profile.title);
318
+ toast.success("Address copied to clipboard!");
319
+ } catch (error) {
320
+ toast.error("Failed to copy address");
321
+ }
322
+ };
323
+
324
+ return (
325
+ <div className="flex items-center gap-1">
326
+ <span
327
+ className={`linked-account-title text-b3-grey font-neue-montreal-semibold ${
328
+ isAddress
329
+ ? "font-mono text-sm" // Use monospace font for addresses
330
+ : "break-words" // Use break-words for emails/names (better than break-all)
331
+ }`}
332
+ title={isAddress ? profile.title : undefined} // Show full address on hover
333
+ >
334
+ {displayTitle}
335
+ </span>
336
+ {isAddress && (
337
+ <button
338
+ onClick={handleCopyAddress}
339
+ className="linked-account-copy-button ml-1 rounded p-1 opacity-0 transition-opacity hover:bg-gray-100 group-hover:opacity-100"
340
+ title="Copy full address"
341
+ >
342
+ <Copy size={12} className="text-gray-500 hover:text-gray-700" />
343
+ </button>
344
+ )}
345
+ </div>
346
+ );
347
+ })()}
293
348
  <span className="linked-account-type text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs">
294
349
  {profile.type.toUpperCase()}
295
350
  </span>