Skip to content
Draft
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_RPC_URL =
NEXT_PUBLIC_RPC_URL =
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
5 changes: 5 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import type { Metadata } from "next";
import "./globals.css";
import { StarknetProvider } from "@/components/StarknetProvider";
import {WalletProvider} from "../context/WalletContext.tsx"

export const metadata: Metadata = {
title: "Escrownet | Secure Escrow Services on Starknet",
Expand Down Expand Up @@ -39,14 +41,17 @@ export default function RootLayout({
<link rel="icon" href="/favicon.ico" />
</head>
<body className="antialiased min-h-screen">
<WalletProvider>
<StarknetProvider>
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:p-4 focus:bg-white focus:z-50 focus:text-black">
Skip to main content
</a>

<div id="main-content">
{children}
</div>
</StarknetProvider>
</WalletProvider>
</body>
</html>
);
Expand Down
62 changes: 62 additions & 0 deletions components/WalletConnector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";
import React, { useState } from "react";
import { useWallet } from "@/context/WalletContext";
import WalletOptionsModal from "./WalletOptionsModal";

function WalletConnector() {
const { isConnected, isConnecting, address, disconnect } = useWallet();
const [showModal, setShowModal] = useState(false);

// Format address for display (show first 6 and last 4 characters)
const formatAddress = (addr: string) => {
if (!addr) return "";
return `${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}`;
};

const handleConnectClick = () => {
setShowModal(true);
};

const handleDisconnect = async () => {
try {
await disconnect();
console.log("Disconnected wallet");
} catch (error) {
console.error("Failed to disconnect wallet:", error);
}
};

const handleCloseModal = () => {
setShowModal(false);
};

return (
<>
{isConnected && address ? (
<div className="flex items-center gap-2">
<div className="px-4 py-2 bg-[#F7F5F9] rounded-lg text-sm">
{formatAddress(address)}
</div>
<button
onClick={handleDisconnect}
className="px-4 py-2 rounded-lg border border-gray-300 hover:bg-[#F7F5F9] transition-colors"
>
Disconnect
</button>
</div>
) : (
<button
onClick={handleConnectClick}
disabled={isConnecting}
className="px-6 py-2 rounded-lg bg-blue-600 text-white hover:bg-blue-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{isConnecting ? "Connecting..." : "Connect Wallet"}
</button>
)}

{showModal && <WalletOptionsModal handleClose={handleCloseModal} />}
</>
);
}

export default WalletConnector;
55 changes: 49 additions & 6 deletions components/WalletOptionsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
"use client";
import React, { useEffect, useRef } from "react";
import LockBodyScroll from "./LockBodyScroll";
import { useConnect } from "@starknet-react/core";
import { useWallet } from "@/context/WalletContext";

function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
// Get wallet functionality from context
const { connect, isConnecting, isInitialized, error } = useWallet();

// Create a ref for the first focusable element

const firstButtonRef = useRef<HTMLButtonElement>(null);
const { connect, connectors } = useConnect();

useEffect(() => {
if (firstButtonRef.current) {
firstButtonRef.current.focus();
}

const handleEscKeyEvent = () => {
handleClose();
};
Expand All @@ -23,6 +27,28 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
};
}, [handleClose]);


// Handler for Invisible SDK (Argent) connection
const handleInvisibleSDK = async () => {
if (!isInitialized) {
console.log("Argent Invisible SDK not initialized yet. Please wait...");
return;
}

try {
await connect();
console.log("Connected with Invisible SDK (Argent)");
handleClose();
} catch (err) {
console.error("Failed to connect with Invisible SDK:", err);
// Error handling is done in the hook
}
};

const handleCartridgeController = () => {
console.log("Cartridge Controller selected");
handleClose();

const handleInvisibleSDK = () => {
console.log("Invisible SDK selected");
handleClose();
Expand All @@ -44,7 +70,7 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
console.error("Failed to connect Cartridge Controller:", error);
}
};

return (
<>
<LockBodyScroll lock={true} />
Expand All @@ -66,7 +92,6 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
>
Choose a Wallet Sign-In Method
</h2>

<button
type="button"
className="inline-flex justify-center items-center gap-[0.625rem] shrink-0 rounded border border-[#D9D9D9]
Expand All @@ -79,11 +104,26 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
</button>
</div>


{!isInitialized && (
<div className="mt-2 p-2 bg-yellow-100 text-yellow-700 rounded-md text-sm">
Initializing wallet connection... Please wait.
</div>
)}

{error && (
<div className="mt-2 p-2 bg-red-100 text-red-700 rounded-md text-sm">
{error.message}
</div>
)}


<div className="flex flex-col gap-y-2 mt-[14px]">
<button
ref={firstButtonRef}
className="bg-[#F7F5F9] rounded-lg px-4 py-[21px] flex items-center gap-x-2 text-base font-medium cursor-pointer hover:bg-[#EFEAF3] transition-colors focus:outline-none focus:ring-2 focus:ring-primaryColor focus:ring-offset-2"
className="bg-[#F7F5F9] rounded-lg px-4 py-[21px] flex items-center gap-x-2 text-base font-medium cursor-pointer hover:bg-[#EFEAF3] transition-colors focus:outline-none focus:ring-2 focus:ring-primaryColor focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
onClick={handleInvisibleSDK}
disabled={isConnecting || !isInitialized}
aria-label="Connect with Invisible SDK"
>
<div
Expand All @@ -106,7 +146,7 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
/>
</svg>
</div>
Use Invisible SDK
{isConnecting ? "Connecting..." : isInitialized ? "Use Argent Wallet" : "Initializing..."}
</button>

<button
Expand Down Expand Up @@ -151,4 +191,7 @@ function WalletOptionsModal({ handleClose }: { handleClose: () => void }) {
);
}


export default WalletOptionsModal;


112 changes: 112 additions & 0 deletions context/WalletContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use client';

import React, { createContext, useContext, ReactNode, useEffect } from 'react';
import useInvisibleSDK from '../hooks/useInvisibleSDK';

// Define the context type
interface WalletContextType {
// Client state
isInitialized: boolean;
isConnecting: boolean;
isConnected: boolean;
address: string | null;
chainId: number | null;
error: Error | null;

// Actions
connect: () => Promise<void>;
disconnect: () => Promise<void>;

// Optional additional wallet information
balance: string | null;
networkName: string | null;
}

// Create context with a default value
const WalletContext = createContext<WalletContextType>({
isInitialized: false,
isConnecting: false,
isConnected: false,
address: null,
chainId: null,
error: null,
connect: async () => {},
disconnect: async () => {},
balance: null,
networkName: null,
});

// Export the context hook for components to use
export const useWallet = () => useContext(WalletContext);

// Network names mapping
const NETWORK_NAMES: Record<number, string> = {
1: 'Ethereum Mainnet',
5: 'Goerli',
11155111: 'Sepolia',
// Add more networks as needed
};

export const WalletProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
// Use the Invisible SDK hook
const {
client,
isInitialized,
isConnecting,
isConnected,
address,
chainId,
error,
connect,
disconnect,
} = useInvisibleSDK();

// Additional wallet state
const [balance, setBalance] = React.useState<string | null>(null);

// Fetch the balance when connected
useEffect(() => {
const fetchBalance = async () => {
if (client && isConnected && address) {
try {
// Implement balance fetching based on your specific needs
// This is just a placeholder - replace with actual implementation
const balanceValue = await client.getBalance?.(address);
setBalance(balanceValue?.toString() || '0');
} catch (err) {
console.error('Failed to fetch balance:', err);
setBalance('Error');
}
} else {
setBalance(null);
}
};

fetchBalance();
}, [client, isConnected, address]);

// Get network name from chainId
const networkName = chainId ? NETWORK_NAMES[chainId] || `Chain ${chainId}` : null;

// Create context value
const contextValue: WalletContextType = {
isInitialized,
isConnecting,
isConnected,
address,
chainId,
error,
connect,
disconnect,
balance,
networkName,
};

return (
<WalletContext.Provider value={contextValue}>
{children}
</WalletContext.Provider>
);
};

export default WalletProvider;
Loading