Skip to content

Commit

Permalink
Merge next into main (#2619)
Browse files Browse the repository at this point in the history
* Fix/donkeys empty balance (#2611)

* Merge into main (#2603)

* Fix missing arrival when sending lords & donkeys (#2594)

* contracts: fix share points check

* contracts: fix share points check

* Add resource filter for contributions (#2596)

* Add resource filter for contributions

* Change % for specific resource contribution

* Fix missing donkeys in bridge step 2 (#2598)

* Fix missing donkeys in bridge step 2

* fix rerenders

* Remove filter

* Fix registration delay (#2602)

* Fix registration delay

* styling

* Open popup on load

* Add burned donkeys back (#2601)

---------

Co-authored-by: Credence <[email protected]>
Co-authored-by: Loaf <[email protected]>

* merge into main (#2605)

* Fix missing arrival when sending lords & donkeys (#2594)

* contracts: fix share points check

* contracts: fix share points check

* Add resource filter for contributions (#2596)

* Add resource filter for contributions

* Change % for specific resource contribution

* Fix missing donkeys in bridge step 2 (#2598)

* Fix missing donkeys in bridge step 2

* fix rerenders

* Remove filter

* Fix registration delay (#2602)

* Fix registration delay

* styling

* Open popup on load

* Add burned donkeys back (#2601)

* im (#2604)

---------

Co-authored-by: Credence <[email protected]>
Co-authored-by: Loaf <[email protected]>

* Fix build (#2610)

* filter out donkeys w/ empty balance

---------

Co-authored-by: Credence <[email protected]>
Co-authored-by: Loaf <[email protected]>

* add stats resources bridged on empire (#2618)

* add resourcers bridged

* change logo

* remove looping gql calls (#2620)

* remove looping gql calls

* batch queries

---------

Co-authored-by: Credence <[email protected]>
Co-authored-by: Loaf <[email protected]>
Co-authored-by: raschel <[email protected]>
  • Loading branch information
4 people authored Dec 28, 2024
1 parent 5cdaad8 commit b73c0c7
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 33 deletions.
37 changes: 17 additions & 20 deletions landing/src/components/modules/bridge-out-step-2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,23 @@ export const BridgeOutStep2 = () => {
const [selectedDonkeys, setSelectedDonkeys] = useState<Set<bigint>>(new Set());
const [isTableOpen, setIsTableOpen] = useState(false);

const updateResourcesFromSelectedDonkeys = useMemo(
() => (selectedDonkeyIds: Set<bigint>) => {
const allResources = Array.from(selectedDonkeyIds).flatMap(
(id) =>
donkeyInfos?.find((d) => d?.donkeyEntityId && BigInt(d.donkeyEntityId) === id)?.donkeyResourceBalances || [],
);
const updateResourcesFromSelectedDonkeys = (selectedDonkeyIds: Set<bigint>) => {
const allResources = Array.from(selectedDonkeyIds).flatMap(
(id) =>
donkeyInfos?.find((d) => d?.donkeyEntityId && BigInt(d.donkeyEntityId) === id)?.donkeyResourceBalances || [],
);

setSelectedResourceIds(allResources.map((r) => r.resourceId as never));
setSelectedResourceAmounts(
allResources.reduce(
(acc, r) => ({
...acc,
[r.resourceId]: (acc[r.resourceId] || 0) + r.amount / RESOURCE_PRECISION,
}),
{},
),
);
},
[donkeyInfos, selectedDonkeys],
);
setSelectedResourceIds(allResources.map((r) => r.resourceId as never));
setSelectedResourceAmounts(
allResources.reduce(
(acc, r) => ({
...acc,
[r.resourceId]: (acc[r.resourceId] || 0) + r.amount / RESOURCE_PRECISION,
}),
{},
),
);
};

useEffect(() => {
const newSelected = new Set<bigint>();
Expand All @@ -115,7 +112,7 @@ export const BridgeOutStep2 = () => {
newSelected.add(BigInt(donkey?.donkeyEntityId || 0));
}
});
}, [donkeyInfos, selectedDonkeys]);
}, [donkeyInfos]);

const handleRefresh = () => {
setIsRefreshing(true);
Expand Down
231 changes: 231 additions & 0 deletions landing/src/components/modules/bridged-resources.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { useDojo } from "@/hooks/context/DojoContext";
import { ResourcesIds } from "@bibliothecadao/eternum";
import { useAccount } from "@starknet-react/core";
import { ArrowDownUp, Pickaxe } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { TypeH2 } from "../typography/type-h2";
import { Card, CardHeader } from "../ui/card";
import { ResourceIcon } from "../ui/elements/ResourceIcon";
import { getSeasonAddresses } from "../ui/utils/utils";

type SortKey = "totalSupply" | "balance";
type SortDirection = "asc" | "desc";

export const BridgedResources = () => {
const [sortKey, setSortKey] = useState<SortKey>("totalSupply");
const [sortDirection, setSortDirection] = useState<SortDirection>("desc");
const [sortedResources, setSortedResources] = useState<[string, [number, string]][]>([]);
const [resourceData, setResourceData] = useState<Record<string, { totalSupply: bigint; balance: bigint }>>({});

useEffect(() => {
const getResources = async () => {
const addresses = await getSeasonAddresses();
setSortedResources(Object.entries(addresses));
};
void getResources();
}, []);

const updateResourceData = useCallback((address: string, totalSupply: bigint, balance: bigint) => {
setResourceData((prev) => {
if (prev[address]?.totalSupply === totalSupply && prev[address]?.balance === balance) {
return prev;
}
return {
...prev,
[address]: { totalSupply, balance },
};
});
}, []);

useEffect(() => {
if (Object.keys(resourceData).length > 0) {
setSortedResources((prev) =>
[...prev].sort((a, b) => {
const aData = resourceData[a[1][1]] || { totalSupply: 0n, balance: 0n };
const bData = resourceData[b[1][1]] || { totalSupply: 0n, balance: 0n };
const comparison = Number(bData[sortKey] - aData[sortKey]);
return sortDirection === "desc" ? comparison : -comparison;
}),
);
}
}, [sortKey, sortDirection, resourceData]);

const handleSort = (key: SortKey) => {
if (sortKey === key) {
setSortDirection(sortDirection === "desc" ? "asc" : "desc");
} else {
setSortKey(key);
setSortDirection("desc");
}
};

return (
<Card className="w-full bg-gray-900 border-gray-800">
<CardHeader className="border-b border-gray-800">
<div className="flex items-center justify-between">
<TypeH2 className="flex items-center gap-3 uppercase text-gold">
<Pickaxe className="w-6 h-6" />
Bridged Resources
</TypeH2>
<div className="flex items-center gap-4">
<button
onClick={() => handleSort("balance")}
className={`text-sm flex items-center gap-2 ${sortKey === "balance" ? "text-gold" : "text-gold/70"}`}
>
<ArrowDownUp
className={`w-4 h-4 ${sortKey === "balance" && sortDirection === "asc" ? "rotate-180" : ""}`}
/>
Sort by Balance
</button>
<button
onClick={() => handleSort("totalSupply")}
className={`text-sm flex items-center gap-2 ${sortKey === "totalSupply" ? "text-gold" : "text-gold/70"}`}
>
<ArrowDownUp
className={`w-4 h-4 ${sortKey === "totalSupply" && sortDirection === "asc" ? "rotate-180" : ""}`}
/>
Sort by Supply
</button>
</div>
</div>
</CardHeader>
<div className="divide-y divide-gray-800">
{sortedResources.map(([key, [id, address]]) => (
<BridgeResource
key={key}
resourceId={id}
name={key}
contractAddress={address}
onDataUpdate={updateResourceData}
/>
))}
</div>
</Card>
);
};

export const BridgeResource = ({
name,
resourceId,
contractAddress,
onDataUpdate,
}: {
name: string;
resourceId: number;
contractAddress: string;
onDataUpdate: (address: string, totalSupply: bigint, balance: bigint) => void;
}) => {
const {
network: { provider },
} = useDojo();
const { account } = useAccount();

const [amountBridged, setAmountBridged] = useState(0n);
const [playerBalance, setPlayerBalance] = useState(0n);
const [copied, setCopied] = useState(false);

useEffect(() => {
const fetchData = async () => {
try {
// Get total supply
const totalSupplyResult = await provider.provider.callContract({
contractAddress: contractAddress,
entrypoint: "total_supply",
});
const totalSupply = BigInt(totalSupplyResult[0]);
setAmountBridged(totalSupply);

// Get player balance
let balance = 0n;
if (account?.address) {
const balanceResult = await provider.provider.callContract({
contractAddress: contractAddress,
entrypoint: "balance_of",
calldata: [account.address],
});
balance = BigInt(balanceResult[0]);
setPlayerBalance(balance);
}

onDataUpdate(contractAddress, totalSupply, balance);
} catch (error) {
console.error("Error fetching data:", error);
setAmountBridged(0n);
setPlayerBalance(0n);
onDataUpdate(contractAddress, 0n, 0n);
}
};

fetchData();
}, [provider, contractAddress, account?.address, onDataUpdate]);

const percentage = amountBridged > 0n ? Number((playerBalance * 100n) / amountBridged) : 0;
const formattedBalance = (Number(playerBalance) / 10 ** 18).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
const formattedSupply = (Number(amountBridged) / 10 ** 18).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});

const copyToClipboard = () => {
navigator.clipboard.writeText(contractAddress);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className="flex items-center justify-between p-6 hover:bg-gray-800/50 transition-colors">
<div className="flex items-center gap-6">
<ResourceIcon size={"xxl"} resource={ResourcesIds[resourceId]} className="w-10 h-10" />
<div className="space-y-1">
<div className="font-bold text-lg text-white">
<a
href={`https://starkscan.co/token/${contractAddress}`}
target="_blank"
rel="noopener noreferrer"
className="hover:text-gold transition-colors"
>
{name}
</a>
</div>
<div className="text-sm text-gray-400">
Balance: <span className="text-gold font-medium">{formattedBalance}</span>{" "}
<span className="text-gray-500">({percentage.toFixed(1)}%)</span>
</div>
<button
onClick={copyToClipboard}
className="text-xs bg-gray-800 hover:bg-gray-700 text-gray-400 px-2 py-1 rounded transition-colors flex items-center gap-1"
title="Click to copy contract address"
>
{contractAddress.slice(0, 6)}...{contractAddress.slice(-4)}
{copied ? (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4 text-green-500"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
)}
</button>
</div>
</div>
<div className="text-right">
<div className="text-sm text-gray-400 mb-1">Total Supply</div>
<div className="font-bold text-lg text-white">{formattedSupply}</div>
</div>
</div>
);
};
2 changes: 1 addition & 1 deletion landing/src/components/ui/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export const getJSONFile = async (filePath: string) => {
return data;
};

interface ResourceAddresses {
export interface ResourceAddresses {
[key: string]: [number, string];
}

Expand Down
4 changes: 2 additions & 2 deletions landing/src/hooks/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import * as types from './graphql';
const documents = {
"\n query getCapacitySpeedConfig($category: Enum!, $entityType: u32!) {\n s0EternumCapacityConfigModels(where: {category: $category }) {\n edges{\n node {\n weight_gram\n }\n }\n }\n s0EternumSpeedConfigModels(where: {entity_type: $entityType }) {\n edges{\n node {\n sec_per_km\n }\n }\n }\n }\n": types.GetCapacitySpeedConfigDocument,
"\n query getEternumOwnerRealmIds($accountAddress: ContractAddress!) {\n s0EternumOwnerModels(where: { address: $accountAddress }, limit: 8000) {\n edges {\n node {\n address\n entity_id\n entity {\n models {\n __typename\n ... on s0_eternum_Realm {\n realm_id\n }\n }\n }\n }\n }\n }\n }\n": types.GetEternumOwnerRealmIdsDocument,
"\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n": types.GetEternumEntityOwnerDocument,
"\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 10000) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n": types.GetEternumEntityOwnerDocument,
"\n query getAccountTokens($accountAddress: String!) {\n tokenBalances(accountAddress: $accountAddress, limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n": types.GetAccountTokensDocument,
"\n query getERC721Mints {\n tokenTransfers(accountAddress: \"0x0\", limit: 8000) {\n edges {\n node {\n tokenMetadata {\n __typename\n ... on ERC721__Token {\n tokenId\n metadataDescription\n imagePath\n contractAddress\n metadata\n }\n }\n }\n }\n }\n }\n": types.GetErc721MintsDocument,
"\n query eternumStatistics {\n s0EternumAddressNameModels {\n totalCount\n }\n s0EternumHyperstructureModels {\n totalCount\n }\n s0EternumRealmModels {\n totalCount\n }\n s0EternumFragmentMineDiscoveredModels {\n totalCount\n }\n }\n": types.EternumStatisticsDocument,
Expand All @@ -43,7 +43,7 @@ export function graphql(source: "\n query getEternumOwnerRealmIds($accountAddre
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEternumEntityOwnerDocument;
export function graphql(source: "\n query getEternumEntityOwner($entityOwnerIds: [u32!]!) {\n s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 10000) {\n edges {\n node {\n entity_id\n entity_owner_id\n entity {\n models {\n __typename\n ... on s0_eternum_OwnedResourcesTracker {\n resource_types\n }\n ... on s0_eternum_Position {\n x\n y\n }\n ... on s0_eternum_ArrivalTime {\n arrives_at\n }\n ... on s0_eternum_Weight {\n value\n }\n }\n }\n }\n }\n }\n }\n"): typeof import('./graphql').GetEternumEntityOwnerDocument;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
2 changes: 1 addition & 1 deletion landing/src/hooks/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7417,7 +7417,7 @@ export const GetEternumEntityOwnerDocument = new TypedDocumentString(`
query getEternumEntityOwner($entityOwnerIds: [u32!]!) {
s0EternumEntityOwnerModels(
where: {entity_owner_idIN: $entityOwnerIds}
limit: 200
limit: 10000
) {
edges {
node {
Expand Down
26 changes: 19 additions & 7 deletions landing/src/hooks/helpers/useDonkeyArrivals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { GET_ETERNUM_ENTITY_OWNERS } from "../query/entityOwners";
import { GET_ENTITY_DISTANCE } from "../query/position";
import { GET_ENTITIES_RESOURCES } from "../query/resources";

const BATCH_SIZE = 5;

export function useDonkeyArrivals(realmEntityIds: ID[]) {
const {
data: entityPositions,
Expand All @@ -29,13 +31,21 @@ export function useDonkeyArrivals(realmEntityIds: ID[]) {
// refetchInterval: 10_000,
// });

const batchedRealmIds = useMemo(() => {
const batches = [];
for (let i = 0; i < realmEntityIds.length; i += BATCH_SIZE) {
batches.push(realmEntityIds.slice(i, i + BATCH_SIZE));
}
return batches;
}, [realmEntityIds]);


const donkeyQueriesResults = useQueries({
queries: realmEntityIds.map((realmId) => ({
queryKey: ["donkeyEntityIds", realmId],
queryFn: () => execute(GET_ETERNUM_ENTITY_OWNERS, { entityOwnerIds: [realmId] }),
enabled: !!realmId,
refetchInterval: 10_000,
staleTime: 5000,
queries: batchedRealmIds.map((realmIds) => ({
queryKey: ["donkeyEntityIds", realmIds],
queryFn: () => execute(GET_ETERNUM_ENTITY_OWNERS, { entityOwnerIds: realmIds }),
enabled: !!realmIds,
staleTime: 5 * 60 * 1000,
})),
});

Expand Down Expand Up @@ -110,7 +120,9 @@ export function useDonkeyArrivals(realmEntityIds: ID[]) {
};

const donkeyInfos = useMemo(() => {
return donkeysAtBank?.map((donkey) => donkey && getDonkeyInfo(donkey));
return donkeysAtBank
?.map((donkey) => donkey && getDonkeyInfo(donkey))
.filter((info) => info?.donkeyResourceBalances.some((balance) => Number(balance.amount) > 0));
}, [donkeysAtBank, donkeyResources]);

return {
Expand Down
2 changes: 1 addition & 1 deletion landing/src/hooks/query/entityOwners.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { graphql } from "../gql";

export const GET_ETERNUM_ENTITY_OWNERS = graphql(`
query getEternumEntityOwner($entityOwnerIds: [u32!]!) {
s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 200) {
s0EternumEntityOwnerModels(where: { entity_owner_idIN: $entityOwnerIds}, limit: 10000) {
edges {
node {
entity_id
Expand Down
10 changes: 9 additions & 1 deletion landing/src/routes/index.lazy.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AnimatedGrid } from "@/components/modules/animated-grid";
import { BridgedResources } from "@/components/modules/bridged-resources";
import { DataCard, DataCardProps } from "@/components/modules/data-card";
import {
PRIZE_POOL_ACHIEVEMENTS,
Expand Down Expand Up @@ -127,7 +128,14 @@ function Index() {
return (
<div className="p-4">
<AnimatedGrid
items={[...dataCards]}
items={[
...dataCards,
{
colSpan: { sm: 2, md: 6, lg: 12 },
rowSpan: { sm: 1, md: 1, lg: 2 },
data: <BridgedResources />,
},
]}
renderItem={(item) =>
React.isValidElement(item.data) ? item.data : <DataCard {...(item.data as DataCardProps)} />
}
Expand Down

0 comments on commit b73c0c7

Please sign in to comment.