Skip to content

Commit

Permalink
Merge pull request #32 from planetarium/use-mimir-for-avatar
Browse files Browse the repository at this point in the history
Use `Mimir` in `avatar/[address]`
  • Loading branch information
boscohyun authored May 17, 2024
2 parents 0d1d03e + c416199 commit 5d854cd
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 70 deletions.
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,54 @@ A web application to provide useful tools. You can access it in https://planetar

## Devlopment

### Configure Environment

- Create `.env` file to project root directory.

```
# Set network config map e.g.,
NETWORK_CONF_MAP="odin=http://9c-main-rpc-1.nine-chronicles.com/graphql,heimdall=http://heimdall-rpc-1.nine-chronicles.com/graphql"
```

### Run

```
yarn install
yarn codegen
yarn dev
```

## Features

### Planets & Networks

NineChronicle's blockchain network is divided into planets, which can be found below.
- main network: https://planets.nine-chronicles.com/planets
- internal network: https://planets-internal.nine-chronicles.com/planets

As you can see above, NineChronicle's planets are divided into main and internal networks for deployment purposes.
Therefore, each of the features provided here must be used separately for each planet and network.

This is a common way to determine what goes into the '[NETWORK]' location in the URL of a feature, which we'll discuss below.

||main|internal|
|:-:|:-:|:-:|
|odin|odin|odin-internal|
|heimdall|heimdall|heimdall-internal|

And since this information is tied to the `NETWORK_CONF_MAP` value you define in your `.env` file, you can change it.

### Show tablesheet in web

`https://planetarium-9c-board.netlify.app/[NETWORK]/tablesheet/[TABLESHEET_NAME]`

For instance, you can see current `StakeRegularRewardSheet` of `mainnet` network in `https://planetarium-9c-board.netlify.app/9c-main/tablesheet/StakeRegularRewardSheet`.
For instance, you can see current `StakeRegularRewardSheet` of `odin`-`mainnet` network in `https://planetarium-9c-board.netlify.app/odin/tablesheet/StakeRegularRewardSheet`.

<img width="880" alt="image" src="https://user-images.githubusercontent.com/26626194/224272344-622e9d80-a74c-48bf-82b6-62f1e8dde3f1.png">

### Show avatar in web

`https://planetarium-9c-board.netlify.app/avatar/[AVATAR_ADDRESS]?<index=[BLOCK_INDEX]>`
`https://planetarium-9c-board.netlify.app/[NETWORK]/avatar/[AVATAR_ADDRESS]?<index=[BLOCK_INDEX]>`

You can see some avatar state in web.

Expand Down
9 changes: 9 additions & 0 deletions apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,12 @@ export async function getSheet(network: string, name: string): Promise<string> {
headers: { accept: "text/csv" },
});
}

export async function getAvatarInventory(network: string, avatarAddress: string): Promise<any | null> {
try {
return await fetchAPI<any>(`${network}/avatars/${avatarAddress}/inventory`);
}
catch (error) {
return null;
}
}
8 changes: 2 additions & 6 deletions pages/[network]/agent/[address].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,15 @@ const AgentPage: NextPage<AgentPageProps> = ({ agent, blockIndex }) => {

export const getServerSideProps: GetServerSideProps<AgentPageProps> = async (context) => {
const address = context.query.address;
if (typeof(address) !== "string") {
if (typeof (address) !== "string") {
throw new Error("Address parameter is not a string.");
}

const sdk = networkToSDK(context);

const blockIndexString = context.query.blockIndex;
const blockIndex = blockIndexString === undefined ? -1 : Number(blockIndexString);
const hash = (await sdk.GetBlockHashByBlockIndex({
index: (blockIndex as unknown) as string, // Break assumption ID must be string.
})).chainQuery.blockQuery?.block?.hash;

const agent = await (await sdk.Agent({address})).stateQuery.agent;
const agent = (await sdk.Agent({ address })).stateQuery.agent;
if (agent === null || agent === undefined) {
return {
props: {
Expand Down
97 changes: 35 additions & 62 deletions pages/[network]/avatar/[address].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextPage, GetServerSideProps } from "next"
import type { BencodexDict, BencodexValue } from "bencodex";
import { getAvatarInventory } from "../../../apiClient";
import { networkToSDK } from "../../../sdk";
import { CurrencyInput } from "../../../generated/graphql-request";

Expand Down Expand Up @@ -49,7 +49,6 @@ interface Avatar {
}

interface Inventory {
migrated: boolean,
items: ItemEntry[],
}

Expand All @@ -72,7 +71,7 @@ interface AvatarPageProps {
avatar: Avatar | null,
}

const AvatarPage: NextPage<AvatarPageProps> = ({avatar}) => {
const AvatarPage: NextPage<AvatarPageProps> = ({ avatar }) => {
if (avatar === null) {
return (
<h1>There is no avatar.</h1>
Expand All @@ -90,7 +89,7 @@ const AvatarPage: NextPage<AvatarPageProps> = ({avatar}) => {
<h1 className="text-3xl font-extrabold mt-10 mb-5">FAVs</h1>
<div className="grid grid-cols-6 content-center">
{
avatar.favs.filter(x => x.amount > 0).map(({ ticker, amount }) =>
avatar.favs.filter(x => x.amount > 0).map(({ ticker, amount }) =>
<div className="" key={ticker}>
<img className="inline m-1 w-10 h-10" title={String(ticker)} src={`https://raw.githubusercontent.com/planetarium/NineChronicles/development/nekoyume/Assets/Resources/UI/Icons/FungibleAssetValue/${ticker}.png`} />
<span className="font-bold">{amount}</span>
Expand All @@ -101,7 +100,7 @@ const AvatarPage: NextPage<AvatarPageProps> = ({avatar}) => {
<h1 className="text-3xl font-extrabold mt-10 mb-5">Inventory</h1>
<div className="flex flex-row flex-wrap space-y-3">
{Array.from(aggregatedItems.entries())
.map(([id, count]) =>
.map(([id, count]) =>
<div className="inline-flex w-28 h-20 border-solid border-2 border-gray-600 content-center" key={id}>
{/* FIXME: MAKE THIS URL CONFIGURABLE BY USER */}
<img className="w-16 h-16" title={String(id)} src={`https://raw.githubusercontent.com/planetarium/NineChronicles/development/nekoyume/Assets/Resources/UI/Icons/Item/${id}.png`} /> <span className="font-bold">{count}</span>
Expand All @@ -113,10 +112,10 @@ const AvatarPage: NextPage<AvatarPageProps> = ({avatar}) => {

export const getServerSideProps: GetServerSideProps<AvatarPageProps> = async (context) => {
const address = context.query.address;
if (typeof(address) !== "string") {
if (typeof (address) !== "string") {
throw new Error("Address parameter is not a string.");
}

const sdk = networkToSDK(context);

const blockIndexString = context.query.blockIndex;
Expand All @@ -125,19 +124,7 @@ export const getServerSideProps: GetServerSideProps<AvatarPageProps> = async (co
index: (blockIndex as unknown) as string, // Break assumption ID must be string.
})).chainQuery.blockQuery?.block?.hash;

const legacyInventoryKey = "inventory" as const;
const inventoryAddress = require("node:crypto").createHmac("sha1", Buffer.from(legacyInventoryKey, "utf8"))
.update(Buffer.from(address.replace("0x", ""), "hex"))
.digest("hex");

const avatar = await (await sdk.Avatar({address})).stateQuery.avatar;

const legacyRawInventoryState = (await sdk.RawState({ address: inventoryAddress, hash })).state;
const migratedRawInventoryState = (await sdk.RawState({ address, hash, accountAddress: "0x000000000000000000000000000000000000001c" })).state;

const rawInventoryState = Buffer.from(migratedRawInventoryState || legacyRawInventoryState, "hex");
const inventory: BencodexDict = require("bencodex").decode(rawInventoryState);

const avatar = (await sdk.Avatar({ address })).stateQuery.avatar;
if (avatar === null || avatar === undefined) {
return {
props: {
Expand All @@ -146,6 +133,9 @@ export const getServerSideProps: GetServerSideProps<AvatarPageProps> = async (co
}
}

const inventoryJsonObj = await getAvatarInventory(context.query.network as string, address);
const inventoryObj = parseToInventory(inventoryJsonObj);

const fetchedFavs = await Promise.all(CURRENCIES.map(currency => sdk.GetBalance({
currency: currency,
address: currency.ticker === "CRYSTAL" ? avatar.agentAddress : address,
Expand All @@ -158,41 +148,37 @@ export const getServerSideProps: GetServerSideProps<AvatarPageProps> = async (co
}
});

function itemMapToObject(item: BencodexDict): Item {
const rawItemId = item.get("itemId") || item.get("item_id");
const rawBuffSkills = item.get("buffSkills");
const rawRequiredBlockIndex = (item.get("requiredBlockIndex") || item.get("rbi")) as number | undefined;
const rawSkills = item.get("skills");
const rawStats = item.get("stats");
const rawStatsMap = item.get("statsMap");
return {
// buffSkills: rawBuffSkills === undefined ? null : rawBuffSkills,
elementalType: item.get("elemental_type") as string,
grade: Number(item.get("grade")),
id: Number(item.get("id")),
itemId: rawItemId === undefined ? null : Buffer.from(rawItemId as Uint8Array).toString("hex"),
itemSubType: item.get("item_sub_type") as string,
itemType: item.get("item_type") as string,
requiredBlockIndex: rawRequiredBlockIndex === undefined ? null : rawRequiredBlockIndex,
// skills: rawSkills === undefined ? null : itemSkillsToObject(rawSkills as BencodexDict),
// stats: rawStats === undefined ? null : itemStatsToObject(rawStats as BencodexDict),
// statsMap: rawStatsMap === undefined ? null : itemStatsMapToObject(rawStatsMap as BencodexDict),
function parseToInventory(inventoryJsonObj: any | null): Inventory {
if (inventoryJsonObj === null) {
return {
items: [],
};
}
}

function itemStatsToObject(stats: BencodexDict): object {
var consumables = inventoryJsonObj.consumables.map(parseToItemEntry);
var costumes = inventoryJsonObj.costumes.map(parseToItemEntry);
var equipments = inventoryJsonObj.equipments.map(parseToItemEntry);
var materials = inventoryJsonObj.materials.map(parseToItemEntry);
return {
type: Object.keys(stats),
items: consumables
.concat(costumes)
.concat(equipments)
.concat(materials),
};
}

// function itemStatsMapToObject(statsMap: BencodexDict): object {
// return Object.fromEntries(Array.from(statsMap.entries()).map(([k, v]: [string, BencodexDict], _) => [k, v]));
// }

function itemSkillsToObject(skills: BencodexDict): object {
function parseToItemEntry(itemJsonObj: any): ItemEntry {
return {
type: typeof(skills)
item: {
elementalType: itemJsonObj.elementalType,
grade: itemJsonObj.grade,
id: itemJsonObj.itemSheetId,
itemId: itemJsonObj.nonFungibleId,
itemSubType: itemJsonObj.itemSubType,
itemType: itemJsonObj.itemType,
requiredBlockIndex: itemJsonObj.requiredBlockIndex,
},
count: itemJsonObj.count,
};
}

Expand All @@ -203,20 +189,7 @@ export const getServerSideProps: GetServerSideProps<AvatarPageProps> = async (co
actionPoint: avatar.actionPoint,
level: avatar.level,
favs,
inventory: {
migrated: migratedRawInventoryState !== null,
items: Array.from(inventory.values())
.map((v: BencodexValue, _) => {
if (!(v instanceof Map)) {
throw new TypeError("Unexpected value type. Please debug.");
}

return {
item: itemMapToObject(v.get("item") as BencodexDict),
count: Number(v.get("count") as BigInt),
};
})
},
inventory: inventoryObj,
}
}
}
Expand Down

0 comments on commit 5d854cd

Please sign in to comment.