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
8,393 changes: 8,133 additions & 260 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@creit.tech/stellar-wallets-kit": "1.9.5",
"@radix-ui/react-accordion": "^1.2.20",
"@radix-ui/react-avatar": "^1.2.6",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-avatar": "^1.2.6",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-progress": "^1.1.16",
"@radix-ui/react-scroll-area": "^1.2.18",
Expand Down
4 changes: 2 additions & 2 deletions src/components/wallet/balance-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useRewards } from "@/lib/hooks/use-rewards";
import { useTokenPrice } from "@/lib/hooks/use-token-price";
import { formatTokenBalance, formatUSD } from "@/lib/utils/format";
import { formatTokenAmount, formatUSD } from "@/lib/utils/format";
import { Coins, ExternalLink, Copy, Check, RefreshCw } from "lucide-react";
import { cn } from "@/lib/utils/cn";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
Expand All @@ -24,7 +24,7 @@ export function BalanceDisplay({ className, compact = false }: BalanceDisplayPro
const issuer = primaryBalance?.tokenIssuer;
const rawBalance = primaryBalance ? parseFloat(primaryBalance.balance) : 0;
const balanceVal = isNaN(rawBalance) ? 0 : rawBalance / Math.pow(10, decimals);
const balanceStr = formatTokenBalance(balanceVal, 0);
const balanceStr = formatTokenAmount(balanceVal, "", { maximumFractionDigits: 2 });

// Derive the deployed address for the current network from the balance's
// issuer when available, so users always have a copyable asset address.
Expand Down
22 changes: 18 additions & 4 deletions src/components/wallet/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,21 @@ import { truncateAddress } from "@/lib/utils/format";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { useState, useRef, useEffect, useCallback } from "react";

const WALLET_LABELS = {
freighter: "Freighter",
lobstr: "LOBSTR",
rabet: "Rabet",
} as const;

export function ConnectButton() {
const { isAuthenticated, walletAddress, isConnecting, connectWallet, disconnect } =
useAuth();
const {
isAuthenticated,
walletAddress,
walletProviderId,
isConnecting,
connectWallet,
disconnect,
} = useAuth();
const { network, setNetwork } = useAuthStore();
const [showDropdown, setShowDropdown] = useState(false);
const [copied, setCopied] = useState(false);
Expand Down Expand Up @@ -89,7 +101,9 @@ export function ConnectButton() {
className="gap-2 font-mono"
>
<span className="flex items-center gap-1.5 font-sans text-xs text-gray-500 dark:text-gray-400">
<span className="font-semibold text-gray-700 dark:text-gray-200">Freighter</span>
<span className="font-semibold text-gray-700 dark:text-gray-200">
{walletProviderId ? WALLET_LABELS[walletProviderId] : "Wallet"}
</span>
<span className="flex items-center gap-1 text-[10px] uppercase font-bold text-stellar-purple px-1.5 py-0.5 rounded bg-stellar-purple/10">
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
{network}
Expand All @@ -108,7 +122,7 @@ export function ConnectButton() {
<div className="px-3 py-2 border-b border-gray-100 dark:border-gray-800">
<div className="text-xs text-gray-400 font-medium">Connected Wallet</div>
<div className="text-sm font-semibold text-gray-800 dark:text-gray-200 flex items-center justify-between">
<span>Freighter Wallet</span>
<span>{walletProviderId ? WALLET_LABELS[walletProviderId] : "Wallet"} Wallet</span>
<span className="text-[10px] text-green-600 bg-green-50 px-1.5 py-0.5 rounded font-mono font-bold">
ACTIVE
</span>
Expand Down
14 changes: 6 additions & 8 deletions src/components/wallet/wallet-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { createContext, useContext, useEffect, type ReactNode } from "react";
import { useAuthStore } from "@/store/auth-store";
import { getFreighterAddress } from "@/lib/stellar/wallet";
import { getWalletAddress } from "@/lib/stellar/wallet";

interface WalletContextValue {
isReady: boolean;
Expand All @@ -20,30 +20,28 @@ interface WalletProviderProps {

/**
* WalletProvider initializes the wallet connection state on mount.
* It checks if Freighter is already connected and syncs with the store.
* It checks whether the selected provider is still connected.
*/
export function WalletProvider({ children }: WalletProviderProps) {
const { isAuthenticated, disconnect } = useAuthStore();
const { isAuthenticated, walletProviderId, network, disconnect } = useAuthStore();

useEffect(() => {
async function checkConnection() {
if (!isAuthenticated) return;

try {
const address = await getFreighterAddress();
// If Freighter returns no address but store says we're connected,
// the user disconnected externally
const address = await getWalletAddress(walletProviderId ?? undefined, network);
if (!address) {
disconnect();
}
} catch (err) {
console.error("Freighter connection check failed:", err);
console.error("Wallet connection check failed:", err);
disconnect();
}
}

checkConnection();
}, [isAuthenticated, disconnect]);
}, [isAuthenticated, walletProviderId, network, disconnect]);

return (
<WalletContext.Provider value={{ isReady: true }}>
Expand Down
27 changes: 14 additions & 13 deletions src/lib/hooks/use-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import { useCallback, useRef, useState } from "react";
import { useAuthStore } from "@/store/auth-store";
import {
connectFreighter,
isFreighterInstalled,
connectWallet as connectStellarWallet,
signChallenge,
getNetworkPassphrase,
} from "@/lib/stellar/wallet";
Expand Down Expand Up @@ -37,9 +36,8 @@ function classifyWalletError(err: unknown, currentNetwork: string): WalletError
) {
return {
type: "not_installed",
message: "Freighter wallet extension is not installed",
resolution:
"Install Freighter from freighter.app and refresh this page.",
message: "No supported Stellar wallet was detected",
resolution: "Install and unlock Freighter, Lobstr, or Rabet, then try again.",
};
}

Expand All @@ -53,8 +51,7 @@ function classifyWalletError(err: unknown, currentNetwork: string): WalletError
return {
type: "user_denied",
message: "Connection request was denied",
resolution:
"Open Freighter and approve the connection request, then try again.",
resolution: "Approve the connection request in your wallet, then try again.",
};
}

Expand All @@ -65,7 +62,7 @@ function classifyWalletError(err: unknown, currentNetwork: string): WalletError
return {
type: "wrong_network",
message: `Wallet is on the wrong network (expected ${currentNetwork})`,
resolution: `Switch your Freighter wallet to ${currentNetwork} and try again.`,
resolution: `Switch your selected wallet to ${currentNetwork} and try again.`,
};
}

Expand All @@ -89,21 +86,21 @@ function classifyWalletError(err: unknown, currentNetwork: string): WalletError
return {
type: "connection_failed",
message: "Failed to connect to wallet",
resolution:
"Make sure Freighter is unlocked and try again. If the problem persists, reinstall the extension.",
resolution: "Make sure your selected wallet is unlocked and try again.",
};
}

return {
type: "unknown",
message: msg || "Connection failed",
resolution: "Please try again. If the problem persists, reinstall the Freighter extension.",
resolution: "Please try again or choose another supported wallet.",
};
}

export function useAuth() {
const {
walletAddress,
walletProviderId,
jwt,
isAuthenticated,
isConnecting,
Expand All @@ -130,6 +127,7 @@ export function useAuth() {
setWalletError(null);

try {
const { address, providerId } = await connectStellarWallet(networkRef.current);
// Check Freighter is installed
const installed = await isFreighterInstalled();
if (!installed) {
Expand All @@ -151,10 +149,11 @@ export function useAuth() {
// Get challenge from backend
const challenge = await getChallenge(address);

// Sign the backend challenge with the selected wallet.
// Sign challenge with Freighter
setConnectionStage("sign");
const passphrase = getNetworkPassphrase(networkRef.current);
const signedChallenge = await signChallenge(challenge, passphrase);
const signedChallenge = await signChallenge(challenge, passphrase, providerId);

// Verify signature and get JWT
setConnectionStage("verify");
Expand All @@ -166,7 +165,8 @@ export function useAuth() {
address,
tokens.accessToken,
tokens.expiresIn,
tokens.refreshToken
tokens.refreshToken,
providerId
);
setConnectionStage("complete");

Expand Down Expand Up @@ -204,6 +204,7 @@ export function useAuth() {

return {
walletAddress,
walletProviderId,
jwt,
isAuthenticated,
isConnecting,
Expand Down
Loading
Loading