Skip to content

Commit

Permalink
Use timestamp to sort leaderboard (#86)
Browse files Browse the repository at this point in the history
* add timestamp

* Add timestamp to leaderboard
Fixes #71

* Test pagination

* Fix page size

* Update cli

* Update CLI

* Fix hardhat

* Remove local try dir

* Fix leaderboard for CLI

* Fix web

* Fix weg

* Fix ordering

* fix code for cli
  • Loading branch information
vrde authored Oct 28, 2024
1 parent 6a9ff4e commit 73b49f6
Show file tree
Hide file tree
Showing 39 changed files with 9,643 additions and 6,979 deletions.
6 changes: 3 additions & 3 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ cd eth
npx hardhat deploy:treasure --network <network> --verify
# Deploy THC
npx hardhat deploy:thc --network <network> --artifacts <artifactsPath> --verify
npx hardhat deploy:thc --network <network> --cbd <cbd-path> --verify
# Copy address to game config in ./try/config.json
# Copy address to game config in cbd/config.json
# Now we are ready to provide the dapp
thc provide-dapp <game-path> <dapp-path>
thc provide-dapp <cbd-path> <dapp-path>
```
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dist0rtion/thc",
"version": "0.0.5",
"version": "0.0.7",
"description": "Treasure Hunt CLI",
"main": "./dist/cli/src/cli.js",
"scripts": {
Expand Down
109 changes: 101 additions & 8 deletions cli/src/TreasureHuntCreator.json

Large diffs are not rendered by default.

130 changes: 37 additions & 93 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#!/usr/bin/env node

import dotenv from "dotenv";
import { Command } from "commander";
import { cp, mkdir, readFile, writeFile } from "fs/promises";
import { main } from "./utils";
import { getWallet, getPublicClient, setRootHash } from "./evm";
import { Config, isHexString } from "./types";
import { cp, mkdir, writeFile } from "fs/promises";
import { load, main } from "./utils";
import { setRootHash, getLeaderboard } from "./evm";
import { isHexString } from "./types";
import path from "path";
import packageJson from "../package.json";
import {
Expand All @@ -23,69 +21,34 @@ program
.description(packageJson.description)
.version(packageJson.version);

const CONFIG_PATH = "thc.json";

program
.command("build <basePath>")
.command("build <cbd>")
.description("Build THC artifacts")
.action(async (basePath: string) => {
await mkdir(getArtifactsPath(basePath), {
.action(async (cbd: string) => {
await mkdir(getArtifactsPath(cbd), {
recursive: true,
});

await main(
path.join(basePath, "chapters"),
getChaptersPath(basePath),
getMetadataPath(basePath),
getRootHashPath(basePath)
path.join(cbd, "chapters"),
getChaptersPath(cbd),
getMetadataPath(cbd),
getRootHashPath(cbd)
);
});

program
.command("set-root-hash <basePath>")
.command("set-root-hash <cbd>")
.description("Update root hash")
.action(async (basePath: string) => {
dotenv.config({ path: path.join(basePath, ".env") });
const endpoint = process.env.ETHEREUM_ENDPOINT;
const privateKey = process.env.PRIVATE_KEY;
const { chainId, thcAddress } = JSON.parse(
await readFile(path.join(basePath, CONFIG_PATH), "utf8")
) as Config;

const rootHash = await readRootHash(basePath);

if (!endpoint) {
console.error("Cannot find 'endpoint' in env");
process.exit(1);
}

if (!privateKey) {
console.error("Cannot find 'privateKey' in env");
process.exit(1);
}

if (!isHexString(privateKey)) {
console.error("'privateKey' should start with '0x'");
process.exit(1);
}

if (!chainId) {
console.error("Cannot find 'chainId' in config");
process.exit(1);
}

if (!isHexString(thcAddress)) {
console.error("Cannot find 'thcAddress' in config");
process.exit(1);
}
.action(async (cbd: string) => {
const { wallet, client, thcAddress, chainId } = await load(cbd);
const rootHash = await readRootHash(cbd);

if (!isHexString(rootHash)) {
console.error("Cannot find 'thcAddress' in config");
process.exit(1);
}

const wallet = getWallet(privateKey, chainId, endpoint);
const client = getPublicClient(chainId, endpoint);
console.log("Wallet address", await wallet.getAddresses());
console.log("Root hash", rootHash);

Expand All @@ -97,57 +60,38 @@ program
});

program
.command("provide-dapp <basePath> <dappPath>")
.description("Copy game artifacts to the dapp")
.action(async (basePath: string, dappPath: string) => {
dotenv.config({ path: path.join(basePath, ".env") });
const endpoint = process.env.ETHEREUM_ENDPOINT;
const { chainId, thcAddress, cname } = JSON.parse(
await readFile(path.join(basePath, CONFIG_PATH), "utf8")
) as Config;

const rootHash = await readRootHash(basePath);

if (!endpoint) {
console.error("Cannot find 'endpoint' in env");
process.exit(1);
}

if (!chainId) {
console.error("Cannot find 'chainId' in config");
process.exit(1);
}

if (!isHexString(thcAddress)) {
console.error("Cannot find 'thcAddress' in config");
process.exit(1);
}
.command("leaderboard <cbd>")
.description("Show leaderboard")
.action(async (cbd: string) => {
const { client, thcAddress, metadata } = await load(cbd);
const leaderboard = await getLeaderboard(
client,
thcAddress,
metadata.keys.length,
metadata.keys.map((x) => x.emoji)
);
console.log(leaderboard);
});

if (!isHexString(rootHash)) {
console.error("Cannot find 'thcAddress' in config");
process.exit(1);
}
program
.command("provide-dapp <cbd> <dappPath>")
.description("Copy game artifacts to the dapp")
.action(async (cbd: string, dappPath: string) => {
const { cname, CONFIG_PATH } = await load(cbd);

// Copy CNAME to public
console.log("porco dio", cname);
console.log(path.join(dappPath, "public", "CNAME"));
await writeFile(path.join(dappPath, "public", "CNAME"), cname);

// Copy config.json to the dapp. The file contains info about the game.
await cp(path.join(basePath, CONFIG_PATH), path.join(dappPath, "thc.json"));
await cp(path.join(cbd, CONFIG_PATH), path.join(dappPath, "thc.json"));

// Copy metadata.json to the dapp. It contains all solution addresses
await cp(
getMetadataPath(basePath),
path.join(dappPath, "src", "metadata.json")
);
await cp(getMetadataPath(cbd), path.join(dappPath, "src", "metadata.json"));

// Copy the encrypted chapters to the dapp
await cp(
getChaptersPath(basePath),
path.join(dappPath, "public", "game-data"),
{ recursive: true }
);
await cp(getChaptersPath(cbd), path.join(dappPath, "public", "game-data"), {
recursive: true,
});
});

program.parse(process.argv);
38 changes: 35 additions & 3 deletions cli/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
getContract,
Hex,
http,
PublicClient,
} from "viem";
import { mainnet, localhost, optimism, sepolia } from "viem/chains";
import TreasureHuntCreatorAbi from "./TreasureHuntCreator.json";
import { privateKeyToAccount } from "viem/accounts";
import { LeaderboardEntry, processLeaderboard } from "./lib";

const ID_TO_CHAIN = {
[mainnet.id]: mainnet,
Expand Down Expand Up @@ -41,7 +41,6 @@ export function getWallet(privateKey: Hex, chainId: number, endpoint: string) {

export function getPublicClient(chainId: number, endpoint: string) {
// If anyone can explain me how to use a wallet client to call
//
return createPublicClient({
chain: mainnet,
transport: http(endpoint),
Expand All @@ -50,10 +49,43 @@ export function getPublicClient(chainId: number, endpoint: string) {

export async function setRootHash(client: Client, address: Hex, rootHash: Hex) {
const contract = getContract({
address: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2",
address,
abi: TreasureHuntCreatorAbi.abi,
client,
});

return await contract.write.setup([rootHash]);
}

export async function getLeaderboard(
client: Client,
address: Hex,
totalKeys: number,
emojis: string[]
) {
const contract = getContract({
address,
abi: TreasureHuntCreatorAbi.abi,
client,
});

let leaderboard: LeaderboardEntry[] = [];
let nextPage: null | number = 0;

for (let i = 0; ; i++) {
const rawLeaderboard: any = await contract.read.getLeaderboard([nextPage]);

({ leaderboard, nextPage } = processLeaderboard(
rawLeaderboard,
nextPage,
leaderboard,
totalKeys,
emojis
));

if (nextPage === null) {
break;
}
}
return leaderboard;
}
7 changes: 6 additions & 1 deletion cli/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ export {
readMetadata,
readRootHash,
} from "../../lib/src/fsUtils";
export { addressFromSolution } from "../../lib/src/thc";
export {
addressFromSolution,
processLeaderboard,
LeaderboardEntry,
Leaderboard,
} from "../../lib/src/thc";
49 changes: 48 additions & 1 deletion cli/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import dotenv from "dotenv";

import { readFile, writeFile, readdir, mkdir, rename } from "fs/promises";
import { promises as fs } from "fs";
import os from "os";
import path from "path";
import { keccak256, toHex } from "viem";
import { addressFromSolution, encrypt, Metadata } from "./lib";
import { addressFromSolution, encrypt, getMetadataPath, Metadata } from "./lib";
import { Config, isHexString } from "./types";
import { getPublicClient, getWallet } from "./evm";

async function inlineImagesInMarkdown(filePath: string): Promise<string> {
try {
Expand Down Expand Up @@ -185,3 +189,46 @@ export async function main(

return chapters;
}

const CONFIG_PATH = "thc.json";

export async function load(cbd: string) {
dotenv.config({ path: path.join(cbd, ".env") });
const endpoint = process.env.ETHEREUM_ENDPOINT;
const privateKey = process.env.PRIVATE_KEY;
const metadata = JSON.parse(
await readFile(getMetadataPath(cbd), "utf8")
) as Metadata;
const { chainId, thcAddress, cname } = JSON.parse(
await readFile(path.join(cbd, CONFIG_PATH), "utf8")
) as Config;

if (!endpoint) {
console.error("Cannot find 'endpoint' in env");
process.exit(1);
}

if (!privateKey) {
console.error("Cannot find 'privateKey' in env");
process.exit(1);
}

if (!isHexString(privateKey)) {
console.error("'privateKey' should start with '0x'");
process.exit(1);
}

if (!chainId) {
console.error("Cannot find 'chainId' in config");
process.exit(1);
}

if (!isHexString(thcAddress)) {
console.error("Cannot find 'thcAddress' in config");
process.exit(1);
}

const wallet = getWallet(privateKey, chainId, endpoint);
const client = getPublicClient(chainId, endpoint);
return { wallet, client, thcAddress, chainId, cname, metadata, CONFIG_PATH };
}
2 changes: 1 addition & 1 deletion cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
Expand Down
5 changes: 5 additions & 0 deletions eth/.openzeppelin/sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
"address": "0xaa01DB22247F746959f853bcfdE4322d414a736e",
"txHash": "0xcf7a282e2822b44f5ccaa115036289e02bddd88af621ab8768571697644b77f5",
"kind": "transparent"
},
{
"address": "0x7D55276A078913a58026887Cf9287F2E172aFA92",
"txHash": "0x0c24f793eb720e94d9100f2134a6ff5a55e32da879314dd10929901c3c391ec5",
"kind": "transparent"
}
],
"impls": {
Expand Down
13 changes: 13 additions & 0 deletions eth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# thc

Deploy `Treasure` contract:

```
npx hardhat --network sepolia deploy:treasure --verify
```

Deploy `THC` contract:

```
npx hardhat --network sepolia deploy:thc --verify --cbd ../../cbd
```
Loading

0 comments on commit 73b49f6

Please sign in to comment.