Skip to content
Open
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
38 changes: 15 additions & 23 deletions frontend/src/components/wallet/WalletModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
/**
* components/wallet/WalletModal.tsx
*
* Wallet selection modal. Shows three wallet cards (Freighter, Albedo, xBull)
* and handles all connecting states and error display.
* Wallet selection modal. Shows the production-ready Freighter connector and
* handles all connecting states and error display.
*
* Albedo and xBull are intentionally hidden until real connectors are
* implemented; the production picker must never create mock sessions.
*
* - Freighter: shows "Install Freighter" link when extension is absent.
* - Albedo: note that a popup window will open.
* - xBull: note for mobile / extension users.
* - Dismiss via Escape key or backdrop click.
*/

Expand All @@ -22,14 +23,15 @@ interface WalletModalProps {
onClose: () => void;
}

const WALLET_NOTES: Partial<Record<WalletId, string>> = {
albedo: "A popup window will open for authentication.",
xbull: "Available as browser extension or mobile app.",
};

export function WalletModal({ onClose }: WalletModalProps) {
const { wallets, status, selectedWalletId, errorMessage, connect, clearError } =
useWallet();
const {
wallets,
status,
selectedWalletId,
errorMessage,
connect,
clearError,
} = useWallet();

const isConnecting = status === "connecting";
const [freighterInstalled, setFreighterInstalled] = React.useState(true);
Expand Down Expand Up @@ -123,11 +125,7 @@ export function WalletModal({ onClose }: WalletModalProps) {
{errorMessage && (
<div className="wallet-error" role="alert">
<span>{errorMessage}</span>
<button
type="button"
className="inline-link"
onClick={clearError}
>
<button type="button" className="inline-link" onClick={clearError}>
Dismiss
</button>
</div>
Expand All @@ -140,8 +138,6 @@ export function WalletModal({ onClose }: WalletModalProps) {
const isConnectingThis = isConnecting && isActiveWallet;
const isFreighter = wallet.id === "freighter";
const notInstalled = isFreighter && !freighterInstalled;
const note = WALLET_NOTES[wallet.id];

return (
<article
key={wallet.id}
Expand All @@ -155,10 +151,6 @@ export function WalletModal({ onClose }: WalletModalProps) {
<span>{wallet.badge}</span>
</header>
<p>{wallet.description}</p>
{note && !notInstalled && (
<p className="wallet-card__note">{note}</p>
)}

{notInstalled ? (
<a
href="https://freighter.app"
Expand Down Expand Up @@ -196,7 +188,7 @@ export function WalletModal({ onClose }: WalletModalProps) {
>
{isConnecting
? "Waiting for wallet approval…"
: "Supported wallets: Freighter, Albedo, xBull"}
: `Supported wallets: ${wallets.map((wallet) => wallet.name).join(", ")}`}
</p>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/context/wallet-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface WalletContextValue {
// so stale persisted sessions are discarded rather than causing type errors.
const STORAGE_KEY = "flowfi.wallet.session.v1";
const WalletContext = createContext<WalletContextValue | undefined>(undefined);
const VALID_WALLET_IDS: WalletId[] = ["freighter", "albedo", "xbull"];
const VALID_WALLET_IDS = SUPPORTED_WALLETS.map((wallet) => wallet.id);

interface WalletState {
status: WalletStatus;
Expand Down Expand Up @@ -133,7 +133,7 @@ function isWalletSession(value: unknown): value is WalletSession {
typeof session.publicKey === "string" &&
typeof session.connectedAt === "string" &&
typeof session.network === "string" &&
typeof session.mocked === "boolean"
session.mocked === false
);
}

Expand Down
83 changes: 13 additions & 70 deletions frontend/src/lib/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
* lib/wallet.ts
*
* Production wallet adapter for FlowFi.
* Supports Freighter (browser extension), Albedo (web auth popup),
* and xBull (extension / mobile handoff) via @creit.tech/stellar-wallets-kit.
* Currently supports Freighter (browser extension).
*
* No mock sessions are created in production paths.
* Albedo and xBull are intentionally not advertised until real connectors are
* implemented. No mock sessions are created in production paths.
* Set NEXT_PUBLIC_STELLAR_NETWORK=TESTNET|MAINNET in .env to control network.
*/

// ── Network configuration ─────────────────────────────────────────────────────

export const STELLAR_NETWORK =
(process.env.NEXT_PUBLIC_STELLAR_NETWORK ?? "TESTNET") as
| "TESTNET"
| "MAINNET";
export const STELLAR_NETWORK = (process.env.NEXT_PUBLIC_STELLAR_NETWORK ??
"TESTNET") as "TESTNET" | "MAINNET";

export const STELLAR_NETWORK_ID =
STELLAR_NETWORK === "MAINNET"
Expand All @@ -28,7 +26,7 @@ import {
getNetworkDetails,
} from "@stellar/freighter-api";

export type WalletId = "freighter" | "albedo" | "xbull";
export type WalletId = "freighter";

export interface WalletDescriptor {
id: WalletId;
Expand Down Expand Up @@ -71,18 +69,6 @@ export const SUPPORTED_WALLETS: readonly WalletDescriptor[] = [
badge: "Extension",
description: "Direct browser wallet for Stellar accounts and Soroban apps.",
},
{
id: "albedo",
name: "Albedo",
badge: "Web",
description: "Connect via web authentication popup. No extension required.",
},
{
id: "xbull",
name: "xBull",
badge: "Extension",
description: "Browser extension and mobile wallet for Stellar ecosystem.",
},
];

// ── Internal helpers ──────────────────────────────────────────────────────────
Expand All @@ -91,7 +77,6 @@ function buildSession(
walletId: WalletId,
publicKey: string,
network: string,
mocked: boolean = false,
): WalletSession {
const descriptor = SUPPORTED_WALLETS.find((w) => w.id === walletId);

Expand All @@ -105,7 +90,7 @@ function buildSession(
publicKey,
connectedAt: new Date().toISOString(),
network,
mocked,
mocked: false,
};
}

Expand All @@ -122,10 +107,14 @@ async function connectFreighter(): Promise<WalletSession> {
const { address, error: addressError } = await getAddress();

if (!address || addressError) {
throw new Error(addressError || "Freighter did not return a valid public key.");
throw new Error(
addressError || "Freighter did not return a valid public key.",
);
}

let networkId =STELLAR_NETWORK_ID.toLowerCase().includes("public") ? "Mainnet" : "Testnet";
let networkId = STELLAR_NETWORK_ID.toLowerCase().includes("public")
? "Mainnet"
: "Testnet";

try {
const details = await getNetworkDetails();
Expand All @@ -146,48 +135,6 @@ async function connectFreighter(): Promise<WalletSession> {
return buildSession("freighter", address, networkId);
}

// ── Albedo (Mock) ──────────────────────────────────────────────────────────────

/**
* Mock connection for Albedo wallet.
* Simulates a connection with a delay to show loading state.
* In production, this would integrate with Albedo's web auth popup.
*/
async function connectAlbedo(): Promise<WalletSession> {
// Simulate connection delay
await new Promise((resolve) => setTimeout(resolve, 1500));

// Generate a mock public key for demonstration
// In production, this would come from Albedo's authentication flow
// Stellar public keys are base32 encoded and 56 characters long, starting with G
const mockPublicKey = "G" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".substring(0, 55);

const networkId = STELLAR_NETWORK === "MAINNET" ? "Mainnet" : "Testnet";

return buildSession("albedo", mockPublicKey, networkId, true);
}

// ── xBull (Mock) ────────────────────────────────────────────────────────────────

/**
* Mock connection for xBull wallet.
* Simulates a connection with a delay to show loading state.
* In production, this would integrate with xBull extension or mobile handoff.
*/
async function connectXBull(): Promise<WalletSession> {
// Simulate connection delay
await new Promise((resolve) => setTimeout(resolve, 1500));

// Generate a mock public key for demonstration
// In production, this would come from xBull's connection flow
// Stellar public keys are base32 encoded and 56 characters long, starting with G
const mockPublicKey = "G" + "ZYXWVUTSRQPONMLKJIHGFEDCBA234567".substring(0, 55);

const networkId = STELLAR_NETWORK === "MAINNET" ? "Mainnet" : "Testnet";

return buildSession("xbull", mockPublicKey, networkId, true);
}

// ── Public connect dispatch ───────────────────────────────────────────────────

export async function connectWallet(
Expand All @@ -196,10 +143,6 @@ export async function connectWallet(
switch (walletId) {
case "freighter":
return connectFreighter();
case "albedo":
return connectAlbedo();
case "xbull":
return connectXBull();
default:
throw new Error("Unsupported wallet selected.");
}
Expand Down
Loading