1
0
forked from mico/idle_moloch

Compare commits

..

1 Commits

Author SHA1 Message Date
yellow
ef1c242471 session wallet 2024-11-01 13:30:56 +01:00
3 changed files with 130 additions and 224 deletions

View File

@ -1,33 +1,24 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react'
import styles from "../styles/Leaderboard.module.css"; import styles from '../styles/Leaderboard.module.css'
import { import { TopEarnersResponse, TopRaidersResponse } from '../types/leaderboard'
TopEarnersResponse, import { formatUnits } from 'viem'
TopRaidersResponse,
PlayerResponse,
} from "../types/leaderboard";
import { formatUnits } from "viem";
const SUBGRAPH_URL = const SUBGRAPH_URL = 'https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-sepolia/version/latest'
"https://api.studio.thegraph.com/query/75782/slay-the-moloch-base-mainnet/version/latest";
const Leaderboard = () => { const Leaderboard = () => {
const [topEarners, setTopEarners] = useState<TopEarnersResponse>(); const [topEarners, setTopEarners] = useState<TopEarnersResponse>()
const [topRaiders, setTopRaiders] = useState<TopRaidersResponse>(); const [topRaiders, setTopRaiders] = useState<TopRaidersResponse>()
const [activeTab, setActiveTab] = useState< const [activeTab, setActiveTab] = useState<'earners' | 'raiders'>('earners')
"earners" | "raiders" | "bosses" | "prestige"
>("earners");
const [bossesDefeated, setBossesDefeated] = useState<PlayerResponse>();
const [playerPrestige, setPlayerPrestige] = useState<PlayerResponse>();
useEffect(() => { useEffect(() => {
const fetchLeaderboards = async () => { const fetchLeaderboards = async () => {
try { try {
// Fetch top earners // Fetch top earners
const earnersResponse = await fetch(SUBGRAPH_URL, { const earnersResponse = await fetch(SUBGRAPH_URL, {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
query: `{ query: `{
armies(first: 10, orderBy: profitPerSecond, orderDirection: desc) { armies(first: 10, orderBy: profitPerSecond, orderDirection: desc) {
player { player {
id id
@ -41,177 +32,83 @@ const Leaderboard = () => {
anointedLevel anointedLevel
championLevel championLevel
} }
}`, }`
}), })
}); })
const earnersData = await earnersResponse.json(); const earnersData = await earnersResponse.json()
setTopEarners({ armies: earnersData.data.armies }); setTopEarners({ armies: earnersData.data.armies })
// Fetch top raiders // Fetch top raiders
const raidersResponse = await fetch(SUBGRAPH_URL, { const raidersResponse = await fetch(SUBGRAPH_URL, {
method: "POST", method: 'POST',
headers: { "Content-Type": "application/json" }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
query: `{ query: `{
players(first: 10, orderBy: numberOfRaids, orderDirection: desc) { players(first: 10, orderBy: numberOfRaids, orderDirection: desc) {
id id
numberOfRaids numberOfRaids
totalMinted totalMinted
currentBalance currentBalance
} }
}`, }`
}), })
}); })
const raidersData = await raidersResponse.json(); const raidersData = await raidersResponse.json()
setTopRaiders({ players: raidersData.data.players }); setTopRaiders({ players: raidersData.data.players })
} catch (error) {
console.error('Error fetching leaderboard:', error)
}
}
// Fetch bosses defeated fetchLeaderboards()
const bossesDefeatedResponse = await fetch(SUBGRAPH_URL, { const interval = setInterval(fetchLeaderboards, 30000) // Refresh every 30 seconds
method: "POST", return () => clearInterval(interval)
headers: { "Content-Type": "application/json" }, }, [])
body: JSON.stringify({
query: `{
players(first: 10, orderBy: bossesDefeated, orderDirection: desc) {
id
bossesDefeated
}
}`,
}),
});
const bossesDefeatedData = await bossesDefeatedResponse.json();
setBossesDefeated({ players: bossesDefeatedData.data.players });
// Fetch player prestige return (
const playerPrestigeResponse = await fetch(SUBGRAPH_URL, { <div className={styles.leaderboard}>
method: "POST", <h2 className={styles.title}>Leaderboard</h2>
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ <div className={styles.tabs}>
query: `{ <button
players(first: 10, orderBy: prestigeLevel, orderDirection: desc) { className={`${styles.tab} ${activeTab === 'earners' ? styles.active : ''}`}
id onClick={() => setActiveTab('earners')}
prestigeLevel >
} Top Earners
}`, </button>
}), <button
}); className={`${styles.tab} ${activeTab === 'raiders' ? styles.active : ''}`}
const playerPrestigeData = await playerPrestigeResponse.json(); onClick={() => setActiveTab('raiders')}
setPlayerPrestige({ players: playerPrestigeData.data.players }); >
} catch (error) { Top Raiders
console.error("Error fetching leaderboard:", error); </button>
} </div>
};
fetchLeaderboards(); {activeTab === 'earners' && (
const interval = setInterval(fetchLeaderboards, 30000); // Refresh every 30 seconds <div className={styles.list}>
return () => clearInterval(interval); {topEarners?.armies.map((army, index) => (
}, []); <div key={army.player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
return ( <span className={styles.address}>{army.player.id.slice(0, 6)}...{army.player.id.slice(-4)}</span>
<div className={styles.leaderboard}> <span className={styles.stat}>{formatUnits(BigInt(army.profitPerSecond), 4)} GELD/s</span>
<h2 className={styles.title}>Leaderboard</h2>
<div className={styles.tabs}>
<button
className={`${styles.tab} ${
activeTab === "earners" ? styles.active : ""
}`}
onClick={() => setActiveTab("earners")}
>
Top Earners
</button>
<button
className={`${styles.tab} ${
activeTab === "raiders" ? styles.active : ""
}`}
onClick={() => setActiveTab("raiders")}
>
Top Raiders
</button>
<button
className={`${styles.tab} ${
activeTab === "bosses" ? styles.active : ""
}`}
onClick={() => setActiveTab("bosses")}
>
Top Boss Slayers
</button>
<button
className={`${styles.tab} ${
activeTab === "prestige" ? styles.active : ""
}`}
onClick={() => setActiveTab("prestige")}
>
Top Players by Prestige
</button>
</div> </div>
))}
{activeTab === "earners" && (
<div className={styles.list}>
{topEarners?.armies.map((army, index) => (
<div key={army.player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
<span className={styles.address}>
{army.player.id.slice(0, 6)}...
{army.player.id.slice(-4)}
</span>
<span className={styles.stat}>
{formatUnits(BigInt(army.profitPerSecond), 4)}{" "}
GELD/s
</span>
</div>
))}
</div>
)}
{activeTab === "raiders" && (
<div className={styles.list}>
{topRaiders?.players.map((player, index) => (
<div key={player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
<span className={styles.address}>
{player.id.slice(0, 6)}...{player.id.slice(-4)}
</span>
<span className={styles.stat}>
{player.numberOfRaids} raids
</span>
</div>
))}
</div>
)}
{activeTab === "bosses" && (
<div className={styles.list}>
{bossesDefeated?.players.map((player, index) => (
<div key={player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
<span className={styles.address}>
{player.id.slice(0, 6)}...{player.id.slice(-4)}
</span>
<span className={styles.stat}>
{player.bossesDefeated} Bosses Slain
</span>
</div>
))}
</div>
)}
{activeTab === "prestige" && (
<div className={styles.list}>
{playerPrestige?.players.map((player, index) => (
<div key={player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
<span className={styles.address}>
{player.id.slice(0, 6)}...{player.id.slice(-4)}
</span>
<span className={styles.stat}>
{player.prestigeLevel} Prestige Level
</span>
</div>
))}
</div>
)}
</div> </div>
); )}
};
export default Leaderboard; {activeTab === 'raiders' && (
<div className={styles.list}>
{topRaiders?.players.map((player, index) => (
<div key={player.id} className={styles.item}>
<span className={styles.rank}>#{index + 1}</span>
<span className={styles.address}>{player.id.slice(0, 6)}...{player.id.slice(-4)}</span>
<span className={styles.stat}>{player.numberOfRaids} raids</span>
</div>
))}
</div>
)}
</div>
)
}
export default Leaderboard

View File

@ -1,31 +1,25 @@
export interface Player { export interface Player {
id: string; id: string
totalMinted: string; totalMinted: string
currentBalance: string; currentBalance: string
numberOfRaids: string; numberOfRaids: string
army?: Army; army?: Army
bossesDefeated?: string; }
prestigeLevel: string;
} export interface Army {
player: Player
export interface Army { profitPerSecond: string
player: Player; projectedDailyEarnings: string
profitPerSecond: string; molochDenierLevel: string
projectedDailyEarnings: string; apprenticeLevel: string
molochDenierLevel: string; anointedLevel: string
apprenticeLevel: string; championLevel: string
anointedLevel: string; }
championLevel: string;
} export interface TopEarnersResponse {
armies: Army[]
export interface TopEarnersResponse { }
armies: Army[];
} export interface TopRaidersResponse {
players: Player[]
export interface TopRaidersResponse { }
players: Player[];
}
export interface PlayerResponse {
players: Player[];
}

View File

@ -25,6 +25,8 @@ contract RaidGeld is ERC20, Ownable, Constants {
mapping(address => Boss) private bosses; mapping(address => Boss) private bosses;
mapping(address => LastBossResult) private lastBossResults; mapping(address => LastBossResult) private lastBossResults;
mapping(address => address) private sessionWallets;
// WETH // WETH
IWETH public immutable weth = IWETH(WETH); IWETH public immutable weth = IWETH(WETH);
// RGCVII token // RGCVII token
@ -92,7 +94,10 @@ contract RaidGeld is ERC20, Ownable, Constants {
} }
modifier onlyActiveSession() { modifier onlyActiveSession() {
require(players[msg.sender].has_active_session, "Session is not active, you need to buy into the game first"); address delegatedPlayer = sessionWallets[msg.sender];
require(
players[msg.sender].has_active_session ||
players[delegatedPlayer].has_active_session, "Session is not active, you need to buy into the game first");
_; _;
} }
@ -200,7 +205,9 @@ contract RaidGeld is ERC20, Ownable, Constants {
// Manual minting for itchy fingers // Manual minting for itchy fingers
function raid() external onlyActiveSession { function raid() external onlyActiveSession {
performRaid(msg.sender); address delegatedPlayer = sessionWallets[msg.sender];
address player = delegatedPlayer == address(0) ? msg.sender : delegatedPlayer;
performRaid(player);
} }
// Helper so we can use it when buying units too // Helper so we can use it when buying units too
@ -240,9 +247,11 @@ contract RaidGeld is ERC20, Ownable, Constants {
// Add a unit to your army // Add a unit to your army
function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession { function addUnit(uint8 unit, uint16 n_units) external onlyActiveSession {
address delegatedPlayer = sessionWallets[msg.sender];
address player = delegatedPlayer == address(0) ? msg.sender : delegatedPlayer;
require(unit <= 3, "Unknown unit"); require(unit <= 3, "Unknown unit");
Army storage army = armies[msg.sender]; Army storage army = armies[player];
uint16 currentLevel = 0; uint16 currentLevel = 0;
if (unit == 0) { if (unit == 0) {
// moloch_denier // moloch_denier
@ -261,13 +270,13 @@ contract RaidGeld is ERC20, Ownable, Constants {
uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units); uint256 cost = RaidGeldUtils.calculateUnitPrice(unit, currentLevel, n_units);
performRaid(msg.sender); performRaid(player);
// TODO: Since we are first minting then burning the token, this could be simplified // TODO: Since we are first minting then burning the token, this could be simplified
require(balanceOf(msg.sender) >= cost, "Not enough GELD to add this unit"); require(balanceOf(player) >= cost, "Not enough GELD to add this unit");
// then burn the cost of the new army // then burn the cost of the new army
_burn(msg.sender, cost); _burn(player, cost);
// Increase level // Increase level
if (unit == 0) { if (unit == 0) {
@ -289,7 +298,7 @@ contract RaidGeld is ERC20, Ownable, Constants {
// Emit event // Emit event
emit UnitAdded( emit UnitAdded(
msg.sender, player,
unit, unit,
n_units, n_units,
cost, cost,
@ -428,6 +437,12 @@ contract RaidGeld is ERC20, Ownable, Constants {
DAO = _dao; DAO = _dao;
} }
function approveSessionWallet(address _wallet) payable external onlyPlayer {
require(!isRegistered(_wallet), "Wallet belongs to other player");
sessionWallets[msg.sender] = _wallet;
payable(_wallet).call{value: msg.value}("");
}
receive() external payable { receive() external payable {
revert("No plain Ether accepted, use register() function to check in :)"); revert("No plain Ether accepted, use register() function to check in :)");
} }