cosmos-connect-react 0.1.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.
@@ -0,0 +1,13 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { Client, ClientState, ClientConfig } from 'cosmos-connect-core';
3
+ interface CosmosContextValue {
4
+ client: Client;
5
+ state: ClientState;
6
+ }
7
+ export interface CosmosProviderProps {
8
+ children: ReactNode;
9
+ config?: Partial<ClientConfig>;
10
+ }
11
+ export declare const CosmosProvider: React.FC<CosmosProviderProps>;
12
+ export declare const useCosmos: () => CosmosContextValue;
13
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useState, } from 'react';
3
+ import { createClient, TERRA_CLASSIC, KeplrWallet, GalaxyStationWallet, LeapWallet, WalletConnectWallet, } from 'cosmos-connect-core';
4
+ const CosmosContext = createContext(null);
5
+ const DEFAULT_CONFIG = {
6
+ chains: [TERRA_CLASSIC],
7
+ wallets: [
8
+ new GalaxyStationWallet(),
9
+ new KeplrWallet(),
10
+ new LeapWallet(),
11
+ new WalletConnectWallet({
12
+ projectId: '39190b939e067ecb6dccdb7c77653a42', // Default placeholder or instructions needed
13
+ }),
14
+ ],
15
+ };
16
+ export const CosmosProvider = ({ children, config, }) => {
17
+ const [client] = useState(() => {
18
+ const fullConfig = {
19
+ chains: config?.chains || DEFAULT_CONFIG.chains,
20
+ wallets: config?.wallets || DEFAULT_CONFIG.wallets,
21
+ storage: config?.storage || DEFAULT_CONFIG.storage,
22
+ };
23
+ return createClient(fullConfig);
24
+ });
25
+ const [state, setState] = useState(client.state);
26
+ useEffect(() => {
27
+ return client.subscribe(setState);
28
+ }, [client]);
29
+ return (_jsx(CosmosContext.Provider, { value: { client, state }, children: children }));
30
+ };
31
+ export const useCosmos = () => {
32
+ const context = useContext(CosmosContext);
33
+ if (!context) {
34
+ throw new Error('useCosmos must be used within a CosmosProvider');
35
+ }
36
+ return context;
37
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface AvatarProps {
3
+ address: string;
4
+ size?: number;
5
+ }
6
+ /**
7
+ * A simple, colorful avatar generated from the address.
8
+ * Mimics the ConnectKit/RainbowKit feel.
9
+ */
10
+ export declare const Avatar: React.FC<AvatarProps>;
11
+ export {};
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * A simple, colorful avatar generated from the address.
4
+ * Mimics the ConnectKit/RainbowKit feel.
5
+ */
6
+ export const Avatar = ({ address, size = 32 }) => {
7
+ // Simple deterministic color generation from address
8
+ const colors = [
9
+ '#FFADAD', '#FFD6A5', '#FDFFB6', '#CAFFBF',
10
+ '#9BF6FF', '#A0C4FF', '#BDB2FF', '#FFC6FF'
11
+ ];
12
+ const getHash = (str) => {
13
+ let hash = 0;
14
+ for (let i = 0; i < str.length; i++) {
15
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
16
+ }
17
+ return hash;
18
+ };
19
+ const colorIndex = Math.abs(getHash(address)) % colors.length;
20
+ const secondaryColorIndex = (colorIndex + 3) % colors.length;
21
+ return (_jsx("div", { style: {
22
+ width: size,
23
+ height: size,
24
+ borderRadius: '50%',
25
+ background: `linear-gradient(135deg, ${colors[colorIndex]} 0%, ${colors[secondaryColorIndex]} 100%)`,
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ fontSize: size * 0.4,
30
+ fontWeight: 'bold',
31
+ color: 'white',
32
+ textShadow: '0 1px 2px rgba(0,0,0,0.1)',
33
+ flexShrink: 0,
34
+ overflow: 'hidden'
35
+ }, children: address.slice(-2).toUpperCase() }));
36
+ };
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const ConnectButton: React.FC;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useAccount } from '../hooks.js';
4
+ import { ConnectModal } from './ConnectModal.js';
5
+ import { Avatar } from './Avatar.js';
6
+ export const ConnectButton = () => {
7
+ const { isConnected, address, status } = useAccount();
8
+ const [isModalOpen, setIsModalOpen] = useState(false);
9
+ const formatAddress = (addr) => {
10
+ return `${addr.slice(0, 8)}...${addr.slice(-4)}`;
11
+ };
12
+ if (isConnected && address) {
13
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { className: "cc-pill", onClick: () => setIsModalOpen(true), children: [_jsx(Avatar, { address: address, size: 24 }), _jsx("span", { className: "cc-pill-text", children: formatAddress(address) })] }), _jsx(ConnectModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false) })] }));
14
+ }
15
+ return (_jsxs(_Fragment, { children: [_jsx("button", { className: "cc-btn", onClick: () => setIsModalOpen(true), disabled: status === 'connecting', children: status === 'connecting' ? 'Connecting...' : 'Connect Wallet' }), _jsx(ConnectModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false) })] }));
16
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface ConnectModalProps {
3
+ isOpen: boolean;
4
+ onClose: () => void;
5
+ }
6
+ export type ModalRoute = 'connectors' | 'about' | 'profile' | 'connecting';
7
+ export declare const ConnectModal: React.FC<ConnectModalProps>;
8
+ export {};
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { AnimatePresence, motion } from 'framer-motion';
4
+ import { useCosmos } from '../CosmosProvider.js';
5
+ import { useConnect, useAccount } from '../hooks.js';
6
+ import Connectors from './Pages/Connectors.js';
7
+ import About from './Pages/About.js';
8
+ import Profile from './Pages/Profile.js';
9
+ import Connecting from './Pages/Connecting.js';
10
+ export const ConnectModal = ({ isOpen, onClose }) => {
11
+ const { client } = useCosmos();
12
+ const { connect } = useConnect();
13
+ const { isConnected, status } = useAccount();
14
+ const [route, setRoute] = useState('connectors');
15
+ const [pendingWallet, setPendingWallet] = useState(null);
16
+ // Sync route with connection status
17
+ useEffect(() => {
18
+ if (isConnected) {
19
+ setRoute('profile');
20
+ }
21
+ else if (status === 'connecting') {
22
+ setRoute('connecting');
23
+ }
24
+ else if (!isOpen) {
25
+ setRoute('connectors');
26
+ }
27
+ }, [isConnected, status, isOpen]);
28
+ if (!isOpen)
29
+ return null;
30
+ const handleWalletSelect = async (wallet) => {
31
+ try {
32
+ setPendingWallet(wallet);
33
+ setRoute('connecting');
34
+ const chainId = client.getChains()[0]?.chainId || 'columbus-5';
35
+ await connect(wallet.id, chainId);
36
+ }
37
+ catch (error) {
38
+ console.error('Failed to connect:', error);
39
+ setRoute('connectors');
40
+ }
41
+ };
42
+ const showBackButton = route !== 'connectors' && route !== 'profile';
43
+ const showInfoButton = route === 'connectors';
44
+ const variants = {
45
+ initial: (direction) => ({
46
+ x: direction > 0 ? 50 : -50,
47
+ opacity: 0,
48
+ }),
49
+ animate: {
50
+ x: 0,
51
+ opacity: 1,
52
+ },
53
+ exit: (direction) => ({
54
+ x: direction < 0 ? 50 : -50,
55
+ opacity: 0,
56
+ }),
57
+ };
58
+ // Direction logic for transitions (simple for now)
59
+ const getDirection = (newRoute) => {
60
+ if (newRoute === 'about' || newRoute === 'connecting')
61
+ return 1;
62
+ return -1;
63
+ };
64
+ const renderPage = () => {
65
+ switch (route) {
66
+ case 'connectors':
67
+ return _jsx(Connectors, { onSelect: handleWalletSelect });
68
+ case 'about':
69
+ return _jsx(About, {});
70
+ case 'profile':
71
+ return _jsx(Profile, {});
72
+ case 'connecting':
73
+ return _jsx(Connecting, { wallet: pendingWallet, onCancel: () => setRoute('connectors') });
74
+ default:
75
+ return null;
76
+ }
77
+ };
78
+ const getTitle = () => {
79
+ switch (route) {
80
+ case 'profile': return 'Account';
81
+ case 'about': return 'About Wallets';
82
+ case 'connecting': return 'Requesting Connection';
83
+ default: return 'Connect Wallet';
84
+ }
85
+ };
86
+ return (_jsx("div", { className: "cc-modal-overlay", onClick: onClose, children: _jsxs(motion.div, { className: "cc-modal-content", onClick: (e) => e.stopPropagation(), initial: { scale: 0.95, opacity: 0 }, animate: { scale: 1, opacity: 1 }, exit: { scale: 0.95, opacity: 0 }, children: [_jsxs("div", { className: "cc-modal-header", children: [showBackButton ? (_jsx("button", { className: "cc-back-btn", onClick: () => setRoute('connectors'), children: "\u2190" })) : showInfoButton ? (_jsx("button", { className: "cc-help-btn", onClick: () => setRoute('about'), title: "About Wallets", children: "?" })) : _jsx("div", { style: { width: 28 } }), _jsx("span", { className: "cc-modal-title", children: getTitle() }), _jsx("button", { className: "cc-close-btn", onClick: onClose, children: "\u00D7" })] }), _jsx("div", { className: "cc-modal-body", children: _jsx(AnimatePresence, { exitBeforeEnter: true, initial: false, children: _jsx(motion.div, { custom: getDirection(route), variants: variants, initial: "initial", animate: "animate", exit: "exit", transition: { duration: 0.2, ease: "easeInOut" }, children: renderPage() }, route) }) })] }) }));
87
+ };
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const About: React.FC;
3
+ export default About;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const About = () => {
3
+ return (_jsxs("div", { className: "cc-page-about", children: [_jsxs("div", { className: "cc-about-steps", children: [_jsxs("div", { className: "cc-about-step", children: [_jsx("div", { className: "cc-step-num", children: "1" }), _jsxs("div", { className: "cc-step-content", children: [_jsx("h4", { children: "Get a Wallet" }), _jsx("p", { children: "A wallet lets you connect to Terra Classic and manage your LUNC assets safely." })] })] }), _jsxs("div", { className: "cc-about-step", children: [_jsx("div", { className: "cc-step-num", children: "2" }), _jsxs("div", { className: "cc-step-content", children: [_jsx("h4", { children: "Add some LUNC" }), _jsx("p", { children: "You'll need some LUNC in your wallet to pay for gas fees on the Terra network." })] })] }), _jsxs("div", { className: "cc-about-step", children: [_jsx("div", { className: "cc-step-num", children: "3" }), _jsxs("div", { className: "cc-step-content", children: [_jsx("h4", { children: "Connect & Stake" }), _jsx("p", { children: "Connect your wallet to this app to start staking, voting, or trading." })] })] })] }), _jsx("button", { className: "cc-primary-btn", onClick: () => window.open('https://www.galaxy-station.app/', '_blank'), children: "Get Galaxy Station" })] }));
4
+ };
5
+ export default About;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface ConnectingProps {
3
+ wallet: any;
4
+ onCancel: () => void;
5
+ }
6
+ declare const Connecting: React.FC<ConnectingProps>;
7
+ export default Connecting;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const Connecting = ({ wallet, onCancel }) => {
3
+ return (_jsx("div", { className: "cc-page-connecting", children: _jsxs("div", { className: "cc-connecting-view", children: [_jsxs("div", { className: "cc-spinner-container", children: [_jsx("div", { className: "cc-spinner" }), wallet?.icon && (_jsx("img", { src: wallet.icon, alt: "", className: "cc-spinner-icon" }))] }), _jsx("h3", { className: "cc-connecting-title", children: "Requesting Connection" }), _jsxs("p", { className: "cc-connecting-text", children: ["Open the ", wallet?.name || 'Wallet', " extension to continue."] }), _jsx("button", { className: "cc-footer-btn", onClick: onCancel, children: "Cancel" })] }) }));
4
+ };
5
+ export default Connecting;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface ConnectorsProps {
3
+ onSelect: (wallet: any) => void;
4
+ }
5
+ declare const Connectors: React.FC<ConnectorsProps>;
6
+ export default Connectors;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCosmos } from '../../CosmosProvider.js';
3
+ const Connectors = ({ onSelect }) => {
4
+ const { client } = useCosmos();
5
+ const wallets = client.getWallets();
6
+ return (_jsx("div", { className: "cc-page-connectors", children: _jsx("div", { className: "cc-wallet-list", children: wallets.map((wallet) => (_jsxs("button", { className: "cc-wallet-item", onClick: () => onSelect(wallet), children: [_jsx("span", { children: wallet.name }), wallet.icon && _jsx("img", { src: wallet.icon, alt: wallet.name, className: "cc-wallet-icon" })] }, wallet.id))) }) }));
7
+ };
8
+ export default Connectors;
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ declare const Profile: React.FC;
3
+ export default Profile;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { useCosmos } from '../../CosmosProvider.js';
4
+ import { useAccount, useDisconnect, useBalance } from '../../hooks.js';
5
+ import { Avatar } from '../Avatar.js';
6
+ const Profile = () => {
7
+ const { client } = useCosmos();
8
+ const { address } = useAccount();
9
+ const { disconnect } = useDisconnect();
10
+ const { balance, isLoading: isBalanceLoading } = useBalance();
11
+ const [copyFeedback, setCopyFeedback] = useState(false);
12
+ const formatBalance = (bal) => {
13
+ if (!bal)
14
+ return '0.00 LUNC';
15
+ const amount = parseFloat(bal.amount) / 1000000;
16
+ return `${amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} LUNC`;
17
+ };
18
+ const handleCopy = () => {
19
+ if (address) {
20
+ navigator.clipboard.writeText(address);
21
+ setCopyFeedback(true);
22
+ setTimeout(() => setCopyFeedback(false), 2000);
23
+ }
24
+ };
25
+ if (!address)
26
+ return null;
27
+ return (_jsx("div", { className: "cc-page-profile", children: _jsxs("div", { className: "cc-profile-view", children: [_jsx("div", { className: "cc-profile-avatar", children: _jsx(Avatar, { address: address, size: 84 }) }), _jsxs("div", { className: "cc-profile-info", children: [_jsxs("h3", { className: "cc-profile-address", children: [address.slice(0, 12), "...", address.slice(-6)] }), _jsx("div", { className: "cc-profile-balance", children: isBalanceLoading ? 'Loading...' : formatBalance(balance) })] }), _jsxs("div", { className: "cc-profile-actions", children: [_jsx("button", { className: "cc-util-btn", onClick: handleCopy, children: copyFeedback ? 'Copied!' : 'Copy Address' }), _jsx("button", { className: "cc-util-btn", onClick: () => disconnect(), style: { color: '#ef4444' }, children: "Disconnect" })] }), _jsxs("div", { className: "cc-profile-footer", children: ["Connected to ", client.getChains()[0]?.chainId] })] }) }));
28
+ };
29
+ export default Profile;
@@ -0,0 +1,22 @@
1
+ export declare const useAccount: () => {
2
+ address: any;
3
+ status: any;
4
+ isConnected: boolean;
5
+ isConnecting: boolean;
6
+ isDisconnected: boolean;
7
+ account: any;
8
+ };
9
+ export declare const useConnect: () => {
10
+ connect: (walletId: string, chainId: string) => Promise<any>;
11
+ };
12
+ export declare const useDisconnect: () => {
13
+ disconnect: () => Promise<any>;
14
+ };
15
+ export declare const useClient: () => Client;
16
+ export declare const useBalance: () => {
17
+ balance: {
18
+ amount: string;
19
+ denom: string;
20
+ } | null;
21
+ isLoading: boolean;
22
+ };
package/dist/hooks.js ADDED
@@ -0,0 +1,62 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useCosmos } from './CosmosProvider.js';
3
+ import { useCallback } from 'react';
4
+ export const useAccount = () => {
5
+ const { state } = useCosmos();
6
+ return {
7
+ address: state.account?.address,
8
+ status: state.status,
9
+ isConnected: state.status === 'connected',
10
+ isConnecting: state.status === 'connecting',
11
+ isDisconnected: state.status === 'disconnected',
12
+ account: state.account,
13
+ };
14
+ };
15
+ export const useConnect = () => {
16
+ const { client } = useCosmos();
17
+ const connect = useCallback(async (walletId, chainId) => {
18
+ return client.connect(walletId, chainId);
19
+ }, [client]);
20
+ return { connect };
21
+ };
22
+ export const useDisconnect = () => {
23
+ const { client } = useCosmos();
24
+ const disconnect = useCallback(async () => {
25
+ return client.disconnect();
26
+ }, [client]);
27
+ return { disconnect };
28
+ };
29
+ export const useClient = () => {
30
+ const { client } = useCosmos();
31
+ return client;
32
+ };
33
+ export const useBalance = () => {
34
+ const { state } = useCosmos();
35
+ const [balance, setBalance] = useState(null);
36
+ const [isLoading, setIsLoading] = useState(false);
37
+ useEffect(() => {
38
+ const address = state.account?.address;
39
+ const rest = state.currentChain?.rest;
40
+ if (address && rest) {
41
+ setIsLoading(true);
42
+ const fetchBalance = async () => {
43
+ try {
44
+ const res = await fetch(`${rest}/cosmos/bank/v1beta1/balances/${address}/by_denom?denom=uluna`);
45
+ const data = await res.json();
46
+ setBalance(data.balance || { amount: '0', denom: 'uluna' });
47
+ }
48
+ catch (error) {
49
+ console.error('Failed to fetch balance:', error);
50
+ }
51
+ finally {
52
+ setIsLoading(false);
53
+ }
54
+ };
55
+ fetchBalance();
56
+ }
57
+ else {
58
+ setBalance(null);
59
+ }
60
+ }, [state.account?.address, state.currentChain]);
61
+ return { balance, isLoading };
62
+ };
@@ -0,0 +1,6 @@
1
+ export * from './CosmosProvider.js';
2
+ export * from './hooks.js';
3
+ export * from './components/ConnectButton.js';
4
+ export * from './components/ConnectModal.js';
5
+ export * from './components/Avatar.js';
6
+ import './styles.css';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './CosmosProvider.js';
2
+ export * from './hooks.js';
3
+ export * from './components/ConnectButton.js';
4
+ export * from './components/ConnectModal.js';
5
+ export * from './components/Avatar.js';
6
+ import './styles.css';
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "cosmos-connect-react",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc"
9
+ },
10
+ "peerDependencies": {
11
+ "cosmos-connect-core": "*",
12
+ "react": ">=18",
13
+ "react-dom": ">=18"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^18.2.0",
17
+ "@types/react-dom": "^18.2.0",
18
+ "cosmos-connect-core": "*",
19
+ "react": "^18.2.0",
20
+ "react-dom": "^18.2.0",
21
+ "typescript": "^5.3.3"
22
+ },
23
+ "dependencies": {
24
+ "framer-motion": "^6.5.1"
25
+ }
26
+ }
@@ -0,0 +1,75 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useState,
6
+ ReactNode,
7
+ } from 'react';
8
+ import {
9
+ Client,
10
+ ClientState,
11
+ createClient,
12
+ ClientConfig,
13
+ TERRA_CLASSIC,
14
+ KeplrWallet,
15
+ GalaxyStationWallet,
16
+ LeapWallet,
17
+ WalletConnectWallet,
18
+ } from 'cosmos-connect-core';
19
+
20
+ interface CosmosContextValue {
21
+ client: Client;
22
+ state: ClientState;
23
+ }
24
+
25
+ const CosmosContext = createContext<CosmosContextValue | null>(null);
26
+
27
+ const DEFAULT_CONFIG: ClientConfig = {
28
+ chains: [TERRA_CLASSIC],
29
+ wallets: [
30
+ new GalaxyStationWallet(),
31
+ new KeplrWallet(),
32
+ new LeapWallet(),
33
+ new WalletConnectWallet({
34
+ projectId: '39190b939e067ecb6dccdb7c77653a42', // Default placeholder or instructions needed
35
+ }),
36
+ ],
37
+ };
38
+
39
+ export interface CosmosProviderProps {
40
+ children: ReactNode;
41
+ config?: Partial<ClientConfig>;
42
+ }
43
+
44
+ export const CosmosProvider: React.FC<CosmosProviderProps> = ({
45
+ children,
46
+ config,
47
+ }) => {
48
+ const [client] = useState(() => {
49
+ const fullConfig: ClientConfig = {
50
+ chains: config?.chains || DEFAULT_CONFIG.chains,
51
+ wallets: config?.wallets || DEFAULT_CONFIG.wallets,
52
+ storage: config?.storage || DEFAULT_CONFIG.storage,
53
+ };
54
+ return createClient(fullConfig);
55
+ });
56
+ const [state, setState] = useState<ClientState>(client.state);
57
+
58
+ useEffect(() => {
59
+ return client.subscribe(setState);
60
+ }, [client]);
61
+
62
+ return (
63
+ <CosmosContext.Provider value={{ client, state }}>
64
+ {children}
65
+ </CosmosContext.Provider>
66
+ );
67
+ };
68
+
69
+ export const useCosmos = () => {
70
+ const context = useContext(CosmosContext);
71
+ if (!context) {
72
+ throw new Error('useCosmos must be used within a CosmosProvider');
73
+ }
74
+ return context;
75
+ };
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+
3
+ interface AvatarProps {
4
+ address: string;
5
+ size?: number;
6
+ }
7
+
8
+ /**
9
+ * A simple, colorful avatar generated from the address.
10
+ * Mimics the ConnectKit/RainbowKit feel.
11
+ */
12
+ export const Avatar: React.FC<AvatarProps> = ({ address, size = 32 }) => {
13
+ // Simple deterministic color generation from address
14
+ const colors = [
15
+ '#FFADAD', '#FFD6A5', '#FDFFB6', '#CAFFBF',
16
+ '#9BF6FF', '#A0C4FF', '#BDB2FF', '#FFC6FF'
17
+ ];
18
+
19
+ const getHash = (str: string) => {
20
+ let hash = 0;
21
+ for (let i = 0; i < str.length; i++) {
22
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
23
+ }
24
+ return hash;
25
+ };
26
+
27
+ const colorIndex = Math.abs(getHash(address)) % colors.length;
28
+ const secondaryColorIndex = (colorIndex + 3) % colors.length;
29
+
30
+ return (
31
+ <div
32
+ style={{
33
+ width: size,
34
+ height: size,
35
+ borderRadius: '50%',
36
+ background: `linear-gradient(135deg, ${colors[colorIndex]} 0%, ${colors[secondaryColorIndex]} 100%)`,
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ justifyContent: 'center',
40
+ fontSize: size * 0.4,
41
+ fontWeight: 'bold',
42
+ color: 'white',
43
+ textShadow: '0 1px 2px rgba(0,0,0,0.1)',
44
+ flexShrink: 0,
45
+ overflow: 'hidden'
46
+ }}
47
+ >
48
+ {address.slice(-2).toUpperCase()}
49
+ </div>
50
+ );
51
+ };
@@ -0,0 +1,44 @@
1
+ import React, { useState } from 'react';
2
+ import { useAccount } from '../hooks.js';
3
+ import { ConnectModal } from './ConnectModal.js';
4
+ import { Avatar } from './Avatar.js';
5
+
6
+ export const ConnectButton: React.FC = () => {
7
+ const { isConnected, address, status } = useAccount();
8
+ const [isModalOpen, setIsModalOpen] = useState(false);
9
+
10
+ const formatAddress = (addr: string) => {
11
+ return `${addr.slice(0, 8)}...${addr.slice(-4)}`;
12
+ };
13
+
14
+ if (isConnected && address) {
15
+ return (
16
+ <>
17
+ <button className="cc-pill" onClick={() => setIsModalOpen(true)}>
18
+ <Avatar address={address} size={24} />
19
+ <span className="cc-pill-text">{formatAddress(address)}</span>
20
+ </button>
21
+ <ConnectModal
22
+ isOpen={isModalOpen}
23
+ onClose={() => setIsModalOpen(false)}
24
+ />
25
+ </>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <>
31
+ <button
32
+ className="cc-btn"
33
+ onClick={() => setIsModalOpen(true)}
34
+ disabled={status === 'connecting'}
35
+ >
36
+ {status === 'connecting' ? 'Connecting...' : 'Connect Wallet'}
37
+ </button>
38
+ <ConnectModal
39
+ isOpen={isModalOpen}
40
+ onClose={() => setIsModalOpen(false)}
41
+ />
42
+ </>
43
+ );
44
+ };