Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement changing adventurer name #276

Merged
merged 9 commits into from
Oct 8, 2024
Merged
80 changes: 47 additions & 33 deletions indexer/src/adventurers.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,60 @@
import type { Config } from "https://esm.sh/@apibara/indexer";
import type { Block, Starknet } from "https://esm.sh/@apibara/indexer/starknet";
import type { Mongo } from "https://esm.sh/@apibara/indexer/sink/mongo";
import type { Console } from "https://esm.sh/@apibara/indexer/sink/console";
import type { Mongo } from "https://esm.sh/@apibara/indexer/sink/mongo";
import type { Block, Starknet } from "https://esm.sh/@apibara/indexer/starknet";
import { MONGO_CONNECTION_STRING } from "./utils/constants.ts";
import { getLevelFromXp } from "./utils/encode.ts";
import {
ADVENTURER_DIED,
ADVENTURER_UPGRADED,
AMBUSHED_BY_BEAST,
ATTACKED_BEAST,
ATTACKED_BY_BEAST,
DISCOVERED_BEAST,
DISCOVERED_GOLD,
DISCOVERED_HEALTH,
DISCOVERED_LOOT,
DODGED_OBSTACLE,
DROPPED_ITEMS,
EQUIPPED_ITEMS,
FLEE_FAILED,
FLEE_SUCCEEDED,
HIT_BY_OBSTACLE,
ITEMS_LEVELED_UP,
parseAdventurerDied,
parseAdventurerUpgraded,
parseAmbushedByBeast,
parseAttackedByBeast,
parseDiscoveredBeast,
parseDiscoveredGold,
parseDiscoveredHealth,
parseStartGame,
START_GAME,
PURCHASED_ITEMS,
ATTACKED_BY_BEAST,
ADVENTURER_DIED,
parseAdventurerDied,
parseAttackedByBeast,
AMBUSHED_BY_BEAST,
parseAmbushedByBeast,
ATTACKED_BEAST,
SLAYED_BEAST,
parseSlayedBeast,
FLEE_FAILED,
parseDiscoveredLoot,
parseDodgedObstacle,
parseDroppedItems,
parseEquippedItems,
parseFleeFailed,
FLEE_SUCCEEDED,
parseFleeSucceeded,
ITEMS_LEVELED_UP,
parseHitByObstacle,
parseItemsLeveledUp,
EQUIPPED_ITEMS,
parsePurchasedItems,
parseEquippedItems,
DROPPED_ITEMS,
parseDroppedItems,
HIT_BY_OBSTACLE,
parseHitByObstacle,
DODGED_OBSTACLE,
parseDodgedObstacle,
UPGRADES_AVAILABLE,
parseSlayedBeast,
parseStartGame,
parseTransfer,
parseUpdateAdventurerName,
parseUpgradesAvailable,
DISCOVERED_BEAST,
parseDiscoveredBeast,
DISCOVERED_LOOT,
parseDiscoveredLoot,
PURCHASED_ITEMS,
SLAYED_BEAST,
START_GAME,
TRANSFER,
parseTransfer,
UPDATE_ADVENTURER_NAME,
UPGRADES_AVAILABLE,
} from "./utils/events.ts";
import {
insertAdventurer,
updateAdventurer,
updateAdventurerName,
updateAdventurerOwner,
} from "./utils/helpers.ts";
import { MONGO_CONNECTION_STRING } from "./utils/constants.ts";
import { getLevelFromXp } from "./utils/encode.ts";

const GAME = Deno.env.get("GAME");
const START = +(Deno.env.get("START") || 0);
Expand Down Expand Up @@ -83,6 +86,7 @@ const filter = {
{ fromAddress: GAME, keys: [UPGRADES_AVAILABLE] },
{ fromAddress: GAME, keys: [ADVENTURER_UPGRADED] },
{ fromAddress: GAME, keys: [TRANSFER] },
{ fromAddress: GAME, keys: [UPDATE_ADVENTURER_NAME] },
],
};

Expand Down Expand Up @@ -352,6 +356,16 @@ export default function transform({ header, events }: Block) {
];
}
}
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
}),
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include timestamp when updating adventurer name for consistency

Consistently including a timestamp in update operations helps maintain a uniform data structure and aids in tracking changes over time. Consider adding a timestamp field to the updateAdventurerName function call.

Suggested change:

            return [
              updateAdventurerName({
                adventurerId: value.adventurerId,
                adventurerName: value.name,
+               timestamp: new Date().toISOString(),
              }),
            ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
}),
];
}
case UPDATE_ADVENTURER_NAME: {
console.log("UPDATE_ADVENTURER_NAME", "->", "ADVENTURER UPDATES");
const { value } = parseUpdateAdventurerName(event.data, 0);
return [
updateAdventurerName({
adventurerId: value.adventurerId,
adventurerName: value.name,
timestamp: new Date().toISOString(),
}),
];
}

default: {
console.warn("Unknown event", event.keys[0]);
return [];
Expand Down
8 changes: 7 additions & 1 deletion indexer/src/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
parseU128,
parseU16,
parseU256,
parseU64,
parseU32,
parseU64,
parseU8,
} from "./parser.ts";

Expand Down Expand Up @@ -53,6 +53,7 @@ export const NEW_COLLECTION_TOTAL = eventKey("NewCollectionTotal");
// Tokens
export const TRANSFER = eventKey("Transfer");
export const CLAIMED_FREE_GAME = eventKey("ClaimedFreeGame");
export const UPDATE_ADVENTURER_NAME = eventKey("UpdateAdventurerName");

export const parseStats = combineParsers({
strength: { index: 0, parser: parseU8 },
Expand Down Expand Up @@ -384,3 +385,8 @@ export const parseNewCollectionTotal = combineParsers({
xp: { index: 1, parser: parseU32 },
gamesPlayed: { index: 2, parser: parseU32 },
});

export const parseUpdateAdventurerName = combineParsers({
adventurerId: { index: 0, parser: parseFelt252 },
name: { index: 1, parser: parseFelt252 },
});
18 changes: 17 additions & 1 deletion indexer/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { checkExistsInt, computeHash, getLevelFromXp } from "./encode.ts";
import { parseAdventurerState } from "./events.ts";
import { computeHash, checkExistsInt, getLevelFromXp } from "./encode.ts";

export function insertAdventurer({
id,
Expand Down Expand Up @@ -235,6 +235,22 @@ export function updateAdventurerOwner({
};
}

export function updateAdventurerName({ adventurerId, adventurerName }: any) {
const entity = {
id: checkExistsInt(parseInt(adventurerId)),
};

return {
entity,
update: {
$set: {
...entity,
name: checkExistsInt(BigInt(adventurerName).toString(16)),
},
},
};
}

export function insertDiscovery({
txHash,
adventurerId,
Expand Down
92 changes: 82 additions & 10 deletions ui/src/app/components/start/AdventurerListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useEffect, useState } from "react";
import Info from "@/app/components/adventurer/Info";
import { Button } from "@/app/components/buttons/Button";
import useAdventurerStore from "@/app/hooks/useAdventurerStore";
import useNetworkAccount from "@/app/hooks/useNetworkAccount";
import useTransactionCartStore from "@/app/hooks/useTransactionCartStore";
import { padAddress } from "@/app/lib/utils";
import { Adventurer } from "@/app/types";
import Info from "@/app/components/adventurer/Info";
import { useProvider } from "@starknet-react/core";
import { ChangeEvent, useEffect, useState } from "react";
import {
AccountInterface,
constants,
Contract,
validateAndParseAddress,
constants,
} from "starknet";
import useTransactionCartStore from "@/app/hooks/useTransactionCartStore";
import { useAccount, useProvider } from "@starknet-react/core";
import useAdventurerStore from "@/app/hooks/useAdventurerStore";
import { padAddress } from "@/app/lib/utils";
import { StarknetIdNavigator } from "starknetid.js";
import { CartridgeIcon, StarknetIdIcon } from "../icons/Icons";

Expand All @@ -25,15 +26,24 @@ export interface AdventurerListCardProps {
from: string,
recipient: string
) => Promise<void>;
changeAdventurerName: (
account: AccountInterface,
adventurerId: number,
name: string,
index: number
) => Promise<void>;
index: number;
}

export const AdventurerListCard = ({
adventurer,
gameContract,
handleSwitchAdventurer,
transferAdventurer,
changeAdventurerName,
index,
}: AdventurerListCardProps) => {
const { account, address } = useAccount();
const { account, address } = useNetworkAccount();
const { provider } = useProvider();
const starknetIdNavigator = new StarknetIdNavigator(
provider,
Expand All @@ -55,6 +65,10 @@ export const AdventurerListCard = ({
"send" | "addToCart" | null
>(null);

const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState("");
const [isMaxLength, setIsMaxLength] = useState(false);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Initialize 'adventurerName' with current name for better user experience

Currently, adventurerName is initialized as an empty string. Consider initializing it with adventurer.name so that the input field is pre-populated with the current name when editing.

Apply this change:

-const [adventurerName, setAdventurerName] = useState("");
+const [adventurerName, setAdventurerName] = useState(adventurer.name || "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState("");
const [isMaxLength, setIsMaxLength] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);
const [adventurerName, setAdventurerName] = useState(adventurer.name || "");
const [isMaxLength, setIsMaxLength] = useState(false);

const setAdventurer = useAdventurerStore((state) => state.setAdventurer);

useEffect(() => {
Expand Down Expand Up @@ -144,6 +158,18 @@ export const AdventurerListCard = ({
setIsTransferOpen(false);
};

const handleNameChange = (
e: ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const value = e.target.value;
setAdventurerName(value.slice(0, 31));
if (value.length >= 31) {
setIsMaxLength(true);
} else {
setIsMaxLength(false);
}
};

Comment on lines +167 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance reusability and maintainability of handleNameChange

The handleNameChange function is well-implemented, but we can improve it further:

  1. Extract the maximum length as a constant:

    const MAX_NAME_LENGTH = 31;
  2. Make the function more reusable by accepting the maximum length as a parameter:

    const handleNameChange = (
      e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
      maxLength: number = MAX_NAME_LENGTH
    ) => {
      const value = e.target.value;
      setAdventurerName(value.slice(0, maxLength));
      setIsMaxLength(value.length >= maxLength);
    };

These changes will make the code more maintainable and flexible for future modifications.

return (
<>
{adventurer && (
Expand All @@ -163,10 +189,18 @@ export const AdventurerListCard = ({
size={"lg"}
variant={"token"}
onClick={() => setIsTransferOpen(!isTransferOpen)}
className={`w-1/2 ${isTransferOpen && "animate-pulse"}`}
className={`w-1/4 ${isTransferOpen && "animate-pulse"}`}
>
Transfer
</Button>
<Button
size={"lg"}
variant={"token"}
onClick={() => setIsEditOpen(!isEditOpen)}
className="w-1/4"
>
Edit
</Button>
{isTransferOpen && (
<>
<div className="absolute bottom-20 bg-terminal-black border border-terminal-green flex flex-col gap-2 items-center justify-center w-3/4 p-2">
Expand Down Expand Up @@ -278,9 +312,47 @@ export const AdventurerListCard = ({
</div>
</>
)}
{isEditOpen && (
<>
<div className="absolute bottom-20 bg-terminal-black border border-terminal-green flex flex-col gap-2 items-center justify-center w-3/4 p-2">
<div className="flex flex-row items-center justify-center w-full">
<span className="uppercase text-2xl text-center flex-grow">
Change Adventurer Name
</span>
</div>
<div className="relative flex flex-col w-full items-center justify-center gap-10">
<input
type="text"
value={adventurerName}
onChange={handleNameChange}
className="p-1 h-12 text-2xl w-3/4 bg-terminal-black border border-terminal-green animate-pulse transform"
/>
{isMaxLength && (
<p className="absolute top-14">MAX LENGTH!</p>
)}
<div className="flex flex-row gap-2 items-center">
<Button
size={"lg"}
onClick={() =>
changeAdventurerName(
account!,
adventurer.id!,
adventurerName,
index
)
}
disabled={adventurerName === ""}
>
Save
</Button>
</div>
</div>
</div>
</>
)}
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Improve null checks and error handling in name editing UI

The new name editing UI is well-structured, but there are some improvements that can be made for robustness:

  1. Handle potential null values for 'account' and 'adventurer.id':

    -onClick={() =>
    -  changeAdventurerName(
    -    account!,
    -    adventurer.id!,
    -    adventurerName,
    -    index
    -  )
    -}
    +onClick={() => {
    +  if (account && adventurer.id) {
    +    changeAdventurerName(account, adventurer.id, adventurerName, index);
    +  } else {
    +    // Handle error (e.g., show an error message)
    +  }
    +}}
  2. Consider adding error handling for the changeAdventurerName function call.

  3. You might want to disable the "Save" button when the new name is the same as the current name to prevent unnecessary API calls.

Committable suggestion was skipped due to low confidence.

)}
{isTransferOpen && (
{(isTransferOpen || isEditOpen) && (
<div className="absolute inset-0 bg-terminal-black/75 z-[1]" />
)}
<Info adventurer={adventurer} gameContract={gameContract} />
Expand Down
9 changes: 9 additions & 0 deletions ui/src/app/components/start/AdventurersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export interface AdventurerListProps {
from: string,
recipient: string
) => Promise<void>;
changeAdventurerName: (
account: AccountInterface,
adventurerId: number,
name: string,
index: number
) => Promise<void>;
}

export const AdventurersList = ({
Expand All @@ -51,6 +57,7 @@ export const AdventurersList = ({
adventurersCount,
aliveAdventurersCount,
transferAdventurer,
changeAdventurerName,
}: AdventurerListProps) => {
const { provider } = useProvider();
const starknetIdNavigator = new StarknetIdNavigator(
Expand Down Expand Up @@ -548,6 +555,8 @@ export const AdventurersList = ({
gameContract={gameContract}
handleSwitchAdventurer={handleSwitchAdventurer}
transferAdventurer={transferAdventurer}
changeAdventurerName={changeAdventurerName}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

changeAdventurerName prop is passed but not used in AdventurerListCard.tsx.

The changeAdventurerName prop is being passed to the AdventurerListCard component in AdventurersList.tsx and other files, but it is not utilized within the AdventurerListCard.tsx component. This may indicate unnecessary code or a missing implementation for handling the name change functionality.

  • File: ui/src/app/components/start/AdventurerListCard.tsx
    • The changeAdventurerName prop is not used or implemented.
🔗 Analysis chain

LGTM! Verify AdventurerListCard implementation.

The changeAdventurerName prop is correctly passed to the AdventurerListCard component, which is consistent with the changes made to the component's interface.

To ensure the complete implementation of the name change functionality, let's verify the AdventurerListCard component:

This script will help verify that the AdventurerListCard component properly implements the name change functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of changeAdventurerName in AdventurerListCard

# Check if AdventurerListCard component uses the changeAdventurerName prop
echo "Checking AdventurerListCard implementation:"
rg --type typescript 'changeAdventurerName.*prop[s]?' ui/src/app/components/start/AdventurerListCard.tsx

# Check for any UI elements or functions related to changing the adventurer's name
echo "Checking for name change UI elements or functions:"
rg --type typescript 'change.*name|rename|updateName' ui/src/app/components/start/AdventurerListCard.tsx

Length of output: 491


Script:

#!/bin/bash
# Description: Verify the implementation of changeAdventurerName in AdventurerListCard.tsx

# Locate the AdventurerListCard.tsx file
echo "Locating AdventurerListCard.tsx:"
ADVENTURER_FILE=$(fd 'AdventurerListCard\.tsx$' ui/src/app/components/start/)

if [ -z "$ADVENTURER_FILE" ]; then
  echo "AdventurerListCard.tsx not found."
  exit 1
fi

echo "Found: $ADVENTURER_FILE"

# Check if AdventurerListCard component uses the changeAdventurerName prop
echo "Checking for 'changeAdventurerName' prop usage in AdventurerListCard.tsx:"
rg 'changeAdventurerName\s*=' "$ADVENTURER_FILE"

# Check for any UI elements or functions related to changing the adventurer's name
echo "Checking for name change UI elements or functions in AdventurerListCard.tsx:"
rg 'change.*name|rename|updateName' "$ADVENTURER_FILE"

Length of output: 892


Script:

#!/bin/bash
# Description: Comprehensive search for 'changeAdventurerName' usage across the codebase

# Search for 'changeAdventurerName' in all .tsx and .ts files
echo "Searching for 'changeAdventurerName' across the entire codebase:"
rg 'changeAdventurerName' --type-add 'typescript:*.ts,*.tsx' --typescript

Length of output: 655


Script:

#!/bin/bash
# Description: Comprehensive search for 'changeAdventurerName' usage across the codebase

# Search for 'changeAdventurerName' in all .ts and .tsx files without using --type flag
echo "Searching for 'changeAdventurerName' in .ts and .tsx files across the codebase:"
rg 'changeAdventurerName' -g '*.ts' -g '*.tsx'

Length of output: 1220

index={selectedIndex}
/>
)}
</div>
Expand Down
8 changes: 8 additions & 0 deletions ui/src/app/containers/AdventurerScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ interface AdventurerScreenProps {
from: string,
recipient: string
) => Promise<void>;
changeAdventurerName: (
account: AccountInterface,
adventurerId: number,
name: string,
index: number
) => Promise<void>;
}

/**
Expand All @@ -50,6 +56,7 @@ export default function AdventurerScreen({
getBalances,
costToPlay,
transferAdventurer,
changeAdventurerName,
}: AdventurerScreenProps) {
const [activeMenu, setActiveMenu] = useState(0);
const setAdventurer = useAdventurerStore((state) => state.setAdventurer);
Expand Down Expand Up @@ -152,6 +159,7 @@ export default function AdventurerScreen({
adventurersCount={adventurersByOwnerCount}
aliveAdventurersCount={aliveAdventurersByOwnerCount}
transferAdventurer={transferAdventurer}
changeAdventurerName={changeAdventurerName}
/>
</div>
)}
Expand Down
4 changes: 1 addition & 3 deletions ui/src/app/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export const getWaitRetryInterval = (network: string) =>
export const ETH_INCREMENT = 0.001;
export const LORDS_INCREMENT = 5;
export const getMaxFee = (network: string) =>
network === "mainnet" || network === "sepolia"
? 0.3 * 10 ** 18
: 0.03 * 10 ** 18; // 0.3ETH on mainnet or sepolia, 0.0003ETH on goerli
network === "mainnet" ? 0.001 * 10 ** 18 : 0.3 * 10 ** 18; // 0.3ETH on mainnet or sepolia, 0.0003ETH on goerli
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Inconsistency Detected Between getMaxFee Implementation and Comments

The getMaxFee function has been updated to return 0.001 ETH for mainnet, while the accompanying comment still mentions 0.3 ETH for mainnet or sepolia. This discrepancy can lead to misunderstandings and potential issues in the application's logic.

Please address the following:

  1. Update the Comment: Ensure the comment accurately reflects the new fee structure in the getMaxFee function.
  2. Review Usage: Although current usages of getMaxFee do not show direct issues, verify that all instances handle the updated fee correctly.
  3. Documentation: Update any related documentation to reflect this significant change in the fee structure.
🔗 Analysis chain

Significant changes to getMaxFee function require clarification and documentation update

The modification to the getMaxFee function introduces several concerns:

  1. There's an inconsistency between the code and the comment. The code now returns 0.001 ETH for mainnet, but the comment still states 0.3 ETH for mainnet or sepolia.

  2. The fee for mainnet has been significantly reduced from 0.3 ETH to 0.001 ETH. This is a 300x reduction, which could have substantial implications.

  3. This change may impact the game's economics or network usage. It's crucial to understand the reasoning behind this reduction and its potential effects.

  4. Notably, this change wasn't mentioned in the PR objectives or the AI-generated summary, which is concerning for such a significant modification.

Please address the following:

  1. Update the comment to accurately reflect the new fee structure.
  2. Provide justification for the significant fee reduction on mainnet.
  3. Assess and document the potential impact of this change on the game's economics and network usage.
  4. Explain why this change wasn't included in the PR description, and consider updating the PR description to include this important modification.

To ensure this change doesn't affect other parts of the codebase, please run the following script:

This will help identify any other locations where the getMaxFee function is used and might need adjustment due to this change.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of getMaxFee function
rg "getMaxFee" --type typescript

Length of output: 67


Script:

#!/bin/bash
# Search for usages of getMaxFee function in .ts files
rg "getMaxFee" --glob "*.ts"

Length of output: 536

export const ETH_PREFUND_AMOUNT = (network: string) =>
network === "mainnet" || network === "sepolia"
? "0x2386F26FC10000"
Expand Down
Loading
Loading