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
10 changes: 8 additions & 2 deletions backend/src/routes/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,14 @@ router.get('/contract/status', verifyAdmin, async (req: Request, res: Response)
...status
});
} catch (err) {
logger.error('Failed to get contract status', { error: (err as Error).message });
res.status(500).json({ success: false, error: (err as Error).message });
logger.error('Failed to get contract status', {
error: (err as Error).message,
stack: (err as Error).stack
});
res.status(500).json({
success: false,
error: (err as Error).message
});
}
});

Expand Down
73 changes: 56 additions & 17 deletions backend/src/services/chain.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,42 +186,72 @@ export class ChainService {

async getHouseBalance() {
try {
const usdcToken = await this.contract.usdcToken();
const chainConfig = getChainConfig(this.chainId);
const usdcToken = chainConfig.usdcAddress;

if (!usdcToken) {
throw new Error(`USDC address not configured for chain ${this.chainId}`);
}

const usdcContract = new ethers.Contract(
usdcToken,
['function balanceOf(address) view returns (uint256)'],
this.provider
);

const contractAddress = await this.contract.getAddress();
console.log("contractAddress", contractAddress);
const balance = await usdcContract.balanceOf(contractAddress);
console.log("balance", balance);

// Convert from USDC decimals (6) to human readable
const balanceInUsdc = Number(balance) / 1e6;

logger.info('House balance retrieved', { balance: balanceInUsdc });
logger.info('House balance retrieved', { balance: balanceInUsdc, contractAddress, usdcToken });

return balanceInUsdc;
} catch (err) {
logger.error('Failed to get house balance', { error: (err as Error).message });
throw err;
logger.error('Failed to get house balance', {
error: (err as Error).message,
stack: (err as Error).stack
});
return 0;
}
}

async getContractStatus() {
try {
const [owner, serverOperator, isPaused, contractAddress, ethBalance] = await Promise.all([
this.contract.owner(),
this.contract.serverOperator(),
this.contract.paused(),
this.contract.getAddress(),
this.provider.getBalance(await this.contract.getAddress())
]);
const contractAddress = await this.contract.getAddress();
const ethBalance = await this.provider.getBalance(contractAddress);

let owner = "0x0000000000000000000000000000000000000000";
let serverOperator = "0x0000000000000000000000000000000000000000";
let isPaused = false;
let usdcToken = "0x0000000000000000000000000000000000000000";
let usdcBalance = 0;

try {
owner = await this.contract.owner();
} catch (err) {
logger.warn('Failed to get owner', { error: (err as Error).message });
}

try {
serverOperator = await this.contract.serverOperator();
} catch (err) {
logger.warn('Failed to get serverOperator', { error: (err as Error).message });
}

try {
isPaused = await this.contract.paused();
} catch (err) {
logger.warn('Failed to get paused status', { error: (err as Error).message });
}

const usdcToken = await this.contract.usdcToken();
const usdcBalance = await this.getHouseBalance();
try {
usdcToken = await this.contract.usdcToken();
usdcBalance = await this.getHouseBalance();
} catch (err) {
logger.warn('Failed to get USDC info', { error: (err as Error).message });
}

return {
owner,
Expand All @@ -233,7 +263,10 @@ export class ChainService {
usdcToken
};
} catch (err) {
logger.error('Failed to get contract status', { error: (err as Error).message });
logger.error('Failed to get contract status', {
error: (err as Error).message,
stack: (err as Error).stack
});
throw err;
}
}
Expand Down Expand Up @@ -283,7 +316,13 @@ export class ChainService {
logger.info('Funding house', { amount, fundAmount: fundAmount.toString() });

// First approve USDC transfer
const usdcToken = await this.contract.usdcToken();
const chainConfig = getChainConfig(this.chainId);
const usdcToken = chainConfig.usdcAddress;

if (!usdcToken) {
throw new Error(`USDC address not configured for chain ${this.chainId}`);
}

const usdcContract = new ethers.Contract(
usdcToken,
[
Expand Down
6 changes: 3 additions & 3 deletions backend/src/services/game-engine.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,9 @@ export class GameEngine {
const targetCrash = generateCrashMultiplier(this.currentRound.serverSeed || '');
const flyingDuration = Math.min(20000, Math.max(2000, targetCrash * 2000));

logger.info(
`Starting flying phase for round ${this.currentRound.roundId} targetCrash=${targetCrash} duration=${flyingDuration}ms`
);
// logger.info(
// `Starting flying phase for round ${this.currentRound.roundId} targetCrash=${targetCrash} duration=${flyingDuration}ms`
// );

this.currentRound.phase = 'FLYING';
this.currentRound.flyStartTime = Date.now();
Expand Down
60 changes: 49 additions & 11 deletions frontend/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@
chain?: string;
}

const SUPPORTED_CHAINS = [
{ id: 8453, label: "Base", color: "from-blue-600 to-blue-700" },
{ id: 42220, label: "Celo", color: "from-green-600 to-emerald-700" },
];

export default function AdminDashboard() {
const chainId = useChainId();
const [adminSecret, setAdminSecret] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [selectedChain, setSelectedChain] = useState<number>(8453); // Default to Base
const [contractStatus, setContractStatus] = useState<ContractStatus | null>(
null,
);
Expand All @@ -61,26 +67,28 @@

const [error, setError] = useState("");

const API_URL =
process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api";

useEffect(() => {
const storedSecret = localStorage.getItem("adminSecret");
const storedChain = localStorage.getItem("adminSelectedChain");
if (storedChain) {
setSelectedChain(Number(storedChain));
}
if (storedSecret) {
setAdminSecret(storedSecret);
verifyAndLogin(storedSecret);
}
}, []);

Check warning on line 80 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

React Hook useEffect has a missing dependency: 'verifyAndLogin'. Either include it or remove the dependency array

const verifyAndLogin = async (secret: string) => {
setIsLoading(true);
setError("");
try {
console.log("secret", secret);
const data = await api.adminFetchContractStatus(secret, chainId);
const data = await api.adminFetchContractStatus(secret, selectedChain);
setContractStatus(data);
setIsAuthenticated(true);
localStorage.setItem("adminSecret", secret);
localStorage.setItem("adminSelectedChain", selectedChain.toString());
} catch (err) {
console.error("Login verification failed:", err);
const errorMsg = (err as Error).message;
Expand All @@ -104,7 +112,7 @@

const fetchContractStatus = async (secret: string) => {
try {
const data = await api.adminFetchContractStatus(secret, chainId);
const data = await api.adminFetchContractStatus(secret, selectedChain);
setContractStatus(data);
} catch (error) {
console.error("Failed to fetch contract status:", error);
Expand All @@ -129,12 +137,12 @@
};

const makeRequest = async (
fn: (...args: any[]) => Promise<any>,

Check warning on line 140 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type

Check warning on line 140 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type
...args: any[]

Check warning on line 141 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type
) => {
setIsLoading(true);
try {
const data = await fn(...args, chainId);
const data = await fn(...args, selectedChain);

addTransaction({
type: "action",
Expand Down Expand Up @@ -264,7 +272,41 @@
</h1>
<p className="text-slate-400">Manage your Aviator contract</p>
</div>
<div className="flex gap-3">
<div className="flex flex-col sm:flex-row gap-3 w-full md:w-auto">
{/* Chain Switcher */}
<div className="flex gap-2 bg-slate-800/50 p-1 rounded-lg border border-slate-700">
{SUPPORTED_CHAINS.map((chain) => (
<button
key={chain.id}
onClick={() => {
setSelectedChain(chain.id);
localStorage.setItem(
"adminSelectedChain",
chain.id.toString(),
);
// Clear all form fields
setWithdrawAmount("");
setFundAmount("");
setNewOperator("");
setEthWithdrawAddress("");
setEthWithdrawAmount("");
// Reset to overview tab
setActiveTab("overview");
// Fetch fresh data for new chain
fetchContractStatus(adminSecret);
}}
disabled={isLoading}
className={`px-4 py-2 rounded-md font-medium transition-all text-sm ${
selectedChain === chain.id
? `bg-gradient-to-r ${chain.color} text-white shadow-lg`
: "text-slate-400 hover:text-white hover:bg-slate-700"
} disabled:opacity-50`}
>
{chain.label}
</button>
))}
</div>

<button
onClick={() => fetchContractStatus(adminSecret)}
disabled={isLoading}
Expand All @@ -275,10 +317,6 @@
/>
Refresh
</button>
<div className="px-4 py-2 bg-slate-800 text-white rounded-lg text-sm font-medium">
{contractStatus?.chain || "Chain"} (ID:{" "}
{contractStatus?.chainId || chainId})
</div>
<button
onClick={handleLogout}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-all"
Expand Down Expand Up @@ -657,7 +695,7 @@
);
}

function TabButton({ active, onClick, children }: any) {

Check warning on line 698 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type
return (
<button
onClick={onClick}
Expand All @@ -672,7 +710,7 @@
);
}

function ActionCard({ title, description, icon, children }: any) {

Check warning on line 713 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type
return (
<div className="bg-slate-900/50 backdrop-blur-xl border border-slate-700/50 rounded-2xl p-6 shadow-2xl">
<div className="flex items-start gap-4 mb-6">
Expand Down Expand Up @@ -748,7 +786,7 @@
copyable,
}: {
label: string;
value: any;

Check warning on line 789 in frontend/app/admin/page.tsx

View workflow job for this annotation

GitHub Actions / frontend

Unexpected any. Specify a different type
copyable?: boolean;
}) {
const [copied, setCopied] = useState(false);
Expand Down
17 changes: 9 additions & 8 deletions frontend/app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@ export default function LeaderboardPage() {
<GameProvider>
<div className="min-h-screen text-white flex flex-col bg-[linear-gradient(90deg,#1a1a1a_50%,#262626_0%)] bg-size-[200px_100%] bg-repeat">
<Nav />
<div className="flex-1 flex flex-col p-6">
<div className="mb-8">
<div className="flex items-center gap-4 mb-4">
<div className="flex-1 flex flex-col p-4 sm:p-6">
<div className="mb-6 sm:mb-8">
<div className="flex items-center gap-2 sm:gap-4 mb-3 sm:mb-4">
<Link
href="/"
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-800/50 border border-green-500/30 hover:bg-green-500/10 hover:border-green-400/50 transition-colors text-sm font-medium text-white"
className="flex items-center gap-2 px-3 sm:px-4 py-2 rounded-lg bg-slate-800/50 border border-green-500/30 hover:bg-green-500/10 hover:border-green-400/50 transition-colors text-xs sm:text-sm font-medium text-white"
>
<ArrowLeft size={18} />
Back to Game
<ArrowLeft size={16} className="sm:w-[18px] sm:h-[18px]" />
<span className="hidden sm:inline">Back to Game</span>
<span className="sm:hidden">Back</span>
</Link>
</div>
<h1 className="text-4xl font-bold text-white mb-2 font-orbitron uppercase tracking-widest">
<h1 className="text-2xl sm:text-4xl font-bold text-white mb-2 font-orbitron uppercase tracking-widest">
Leaderboard
</h1>
<div className="h-1 w-24 bg-gradient-to-r from-green-500 to-emerald-600 rounded"></div>
<div className="h-1 w-20 sm:w-24 bg-gradient-to-r from-green-500 to-emerald-600 rounded"></div>
</div>
<Leaderboard />
</div>
Expand Down
49 changes: 48 additions & 1 deletion frontend/components/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,54 @@ const Leaderboard: React.FC = () => {

return (
<div className="w-full max-w-4xl mx-auto">
<div className="bg-black/50 backdrop-blur-sm border border-green-500/30 rounded-lg overflow-hidden">
{/* Mobile Card Layout */}
<div className="sm:hidden space-y-3">
{leaderboard.length === 0 ? (
<div className="px-4 py-8 text-center text-gray-400 font-courier text-sm bg-black/50 backdrop-blur-sm border border-green-500/30 rounded-lg">
No leaderboard data yet. Start playing to appear here!
</div>
) : (
<>
{leaderboard.map((entry: any, idx: number) => (
<div
key={entry.address}
className={`px-4 py-4 rounded-lg backdrop-blur-sm border transition-all ${getRowStyle(idx + 1)}`}
>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="flex-shrink-0">
{getMedalIcon(idx + 1) || (
<span className="text-lg font-bold text-gray-400 w-8 text-center font-orbitron">
#{idx + 1}
</span>
)}
</div>
<div className="min-w-0 flex-1">
<div className="font-mono text-xs text-gray-400 font-courier truncate">
{entry.address.slice(0, 6)}...{entry.address.slice(-4)}
</div>
</div>
</div>
<div className="text-right flex-shrink-0">
<div className="text-sm font-bold text-green-400 font-orbitron">
+{entry.totalWon}
</div>
<div className="text-xs text-gray-500 font-courier">
USDC
</div>
</div>
</div>
</div>
))}
<div className="px-4 py-3 text-xs text-gray-400 font-courier text-center bg-black/30 rounded-lg border border-green-500/20">
Total Players: {leaderboard.length}
</div>
</>
)}
</div>

{/* Desktop Table Layout */}
<div className="hidden sm:block bg-black/50 backdrop-blur-sm border border-green-500/30 rounded-lg overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-green-500/20 to-emerald-600/20 border-b border-green-500/30 px-6 py-4">
<div className="flex items-center justify-between text-sm font-semibold text-green-400 font-orbitron uppercase tracking-wider">
Expand Down
Loading
Loading