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.
- package/dist/CosmosProvider.d.ts +13 -0
- package/dist/CosmosProvider.js +37 -0
- package/dist/components/Avatar.d.ts +11 -0
- package/dist/components/Avatar.js +36 -0
- package/dist/components/ConnectButton.d.ts +2 -0
- package/dist/components/ConnectButton.js +16 -0
- package/dist/components/ConnectModal.d.ts +8 -0
- package/dist/components/ConnectModal.js +87 -0
- package/dist/components/Pages/About.d.ts +3 -0
- package/dist/components/Pages/About.js +5 -0
- package/dist/components/Pages/Connecting.d.ts +7 -0
- package/dist/components/Pages/Connecting.js +5 -0
- package/dist/components/Pages/Connectors.d.ts +6 -0
- package/dist/components/Pages/Connectors.js +8 -0
- package/dist/components/Pages/Profile.d.ts +3 -0
- package/dist/components/Pages/Profile.js +29 -0
- package/dist/hooks.d.ts +22 -0
- package/dist/hooks.js +62 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/package.json +26 -0
- package/src/CosmosProvider.tsx +75 -0
- package/src/components/Avatar.tsx +51 -0
- package/src/components/ConnectButton.tsx +44 -0
- package/src/components/ConnectModal.tsx +138 -0
- package/src/components/Pages/About.tsx +37 -0
- package/src/components/Pages/Connecting.tsx +30 -0
- package/src/components/Pages/Connectors.tsx +30 -0
- package/src/components/Pages/Profile.tsx +66 -0
- package/src/hooks.ts +73 -0
- package/src/index.ts +6 -0
- package/src/styles.css +329 -0
- package/tsconfig.json +16 -0
|
@@ -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,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,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,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,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,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,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;
|
package/dist/hooks.d.ts
ADDED
|
@@ -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
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
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
|
+
};
|