From df13a8fc787210fdb7ed4293eb7412b659c6ae25 Mon Sep 17 00:00:00 2001
From: yawn <69970183+yawn-c111@users.noreply.github.com>
Date: Mon, 30 Sep 2024 21:39:25 +0900
Subject: [PATCH 1/6] feat: viem client and sendEth

---
 pkgs/cli/src/commands/wallet.ts | 18 +++++-----
 pkgs/cli/src/modules/viem.ts    | 58 ++++++++++++++++++++++++++-------
 2 files changed, 54 insertions(+), 22 deletions(-)

diff --git a/pkgs/cli/src/commands/wallet.ts b/pkgs/cli/src/commands/wallet.ts
index 6da93ab..1c0b2f0 100644
--- a/pkgs/cli/src/commands/wallet.ts
+++ b/pkgs/cli/src/commands/wallet.ts
@@ -1,11 +1,9 @@
 import { Command } from "commander";
 import { getEthAddress, sendEth } from "../modules/viem";
-import { listProfiles, saveProfile } from "../services/wallet";
+import { getWallet, listProfiles, saveProfile } from "../services/wallet";
 
 export const walletCommands = new Command();
 
-const { TOBAN_PRIVATE_KEY } = process.env;
-
 walletCommands
 	.name("wallet")
 	.description("This is a CLI function for toban project")
@@ -39,11 +37,11 @@ walletCommands
 walletCommands
 	.command("sendEth")
 	.description("Send ETH")
-	.action(async () => {
-		console.log("Start send ETH");
-		const address1 = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
-
-		await sendEth(TOBAN_PRIVATE_KEY as `0x${string}`, address1);
-
-		console.log("End send ETH");
+	.requiredOption("--name <name>", "Wallet name")
+	.requiredOption("--receiver <receiver>", "Receiver address")
+	.requiredOption("--amount <amount>", "Amount")
+	.option("--chainId <chainId>", "chainId")
+	.action(async ({ name, receiver, amount, chainId }) => {
+		const account = getWallet(name);
+		await sendEth(account, receiver, amount, chainId);
 	});
diff --git a/pkgs/cli/src/modules/viem.ts b/pkgs/cli/src/modules/viem.ts
index 0f703c6..6387f09 100644
--- a/pkgs/cli/src/modules/viem.ts
+++ b/pkgs/cli/src/modules/viem.ts
@@ -1,28 +1,62 @@
-import { createWalletClient, http, parseEther } from "viem";
+import {
+	Chain,
+	createWalletClient,
+	http,
+	parseEther,
+	PrivateKeyAccount,
+} from "viem";
 import { privateKeyToAccount } from "viem/accounts";
-import { hardhat } from "viem/chains";
+import { hardhat, sepolia, holesky } from "viem/chains";
 
-/**
- * ETHを送金するためのメソッド
- * @param secretKey
- * @param to
- * @returns
- */
-export const sendEth = async (secretKey: `0x${string}`, to: `0x${string}`) => {
-	const account = privateKeyToAccount(secretKey);
+const chains = [hardhat, sepolia, holesky];
+
+function getChainById(chainId: number | string): Chain {
+	const numericChainId = Number(chainId);
+
+	const chain = chains.find((c) => c.id === numericChainId);
+
+	if (!chain) {
+		throw new Error(`Chain with id ${numericChainId} not found`);
+	}
+
+	return chain;
+}
+
+export const setClient = async (
+	account: PrivateKeyAccount,
+	chainId?: number | undefined
+) => {
+	const chain = chainId ? getChainById(chainId) : holesky;
 
 	const client = createWalletClient({
 		account,
-		chain: hardhat,
+		chain,
 		transport: http(),
 	});
 
+	return client;
+};
+
+export const sendEth = async (
+	account: PrivateKeyAccount,
+	to: `0x${string}`,
+	amount: string,
+	chainId?: number
+) => {
+	const client = await setClient(account, chainId);
+
 	const hash = await client.sendTransaction({
 		account,
 		to: to,
-		value: parseEther("0.001"),
+		value: parseEther(amount),
 	});
 
+	console.log(`Transaction sent: ${hash}`);
+	console.log(`From: ${account.address}`);
+	console.log(`To: ${to}`);
+	console.log(`Amount: ${amount} ETH`);
+	console.log(`Chain ID: ${client.chain.id}`);
+
 	return hash;
 };
 

From 015c0b998cb9e4dfec35635293da1b025a8db30f Mon Sep 17 00:00:00 2001
From: yawn <69970183+yawn-c111@users.noreply.github.com>
Date: Mon, 30 Sep 2024 22:15:43 +0900
Subject: [PATCH 2/6] update: getWallet and sendEth

---
 pkgs/cli/src/commands/wallet.ts |  6 +++---
 pkgs/cli/src/modules/viem.ts    | 25 +++++++++++++++----------
 pkgs/cli/src/services/wallet.ts | 11 +++++++++--
 3 files changed, 27 insertions(+), 15 deletions(-)

diff --git a/pkgs/cli/src/commands/wallet.ts b/pkgs/cli/src/commands/wallet.ts
index 17d9450..9bcc2c6 100644
--- a/pkgs/cli/src/commands/wallet.ts
+++ b/pkgs/cli/src/commands/wallet.ts
@@ -57,7 +57,7 @@ walletCommands
 	.requiredOption("--receiver <receiver>", "Receiver address")
 	.requiredOption("--amount <amount>", "Amount")
 	.option("--chainId <chainId>", "chainId")
-	.action(async ({ name, receiver, amount, chainId }) => {
-		const account = getWallet(name);
-		await sendEth(account, receiver, amount, chainId);
+	.action(async ({ name, receiver, amount }) => {
+		const wallet = await getWallet(name);
+		await sendEth(wallet, receiver, amount);
 	});
diff --git a/pkgs/cli/src/modules/viem.ts b/pkgs/cli/src/modules/viem.ts
index 6387f09..35bab5f 100644
--- a/pkgs/cli/src/modules/viem.ts
+++ b/pkgs/cli/src/modules/viem.ts
@@ -4,6 +4,7 @@ import {
 	http,
 	parseEther,
 	PrivateKeyAccount,
+	WalletClient,
 } from "viem";
 import { privateKeyToAccount } from "viem/accounts";
 import { hardhat, sepolia, holesky } from "viem/chains";
@@ -22,40 +23,44 @@ function getChainById(chainId: number | string): Chain {
 	return chain;
 }
 
-export const setClient = async (
+export const setWallet = async (
 	account: PrivateKeyAccount,
 	chainId?: number | undefined
 ) => {
 	const chain = chainId ? getChainById(chainId) : holesky;
 
-	const client = createWalletClient({
+	const wallet = createWalletClient({
 		account,
 		chain,
 		transport: http(),
 	});
 
-	return client;
+	return wallet;
 };
 
 export const sendEth = async (
-	account: PrivateKeyAccount,
+	wallet: WalletClient,
 	to: `0x${string}`,
-	amount: string,
-	chainId?: number
+	amount: string
 ) => {
-	const client = await setClient(account, chainId);
+	const account = wallet.account;
+
+	if (!account) {
+		throw new Error("Client account is not defined");
+	}
 
-	const hash = await client.sendTransaction({
+	const hash = await wallet.sendTransaction({
 		account,
-		to: to,
+		to,
 		value: parseEther(amount),
+		chain: wallet.chain,
 	});
 
 	console.log(`Transaction sent: ${hash}`);
 	console.log(`From: ${account.address}`);
 	console.log(`To: ${to}`);
 	console.log(`Amount: ${amount} ETH`);
-	console.log(`Chain ID: ${client.chain.id}`);
+	console.log(`Chain ID: ${wallet.chain?.id}`);
 
 	return hash;
 };
diff --git a/pkgs/cli/src/services/wallet.ts b/pkgs/cli/src/services/wallet.ts
index 3ed470f..b96e838 100644
--- a/pkgs/cli/src/services/wallet.ts
+++ b/pkgs/cli/src/services/wallet.ts
@@ -3,6 +3,7 @@ import path from "path";
 import { Hex } from "viem";
 import { privateKeyToAccount, privateKeyToAddress } from "viem/accounts";
 const profilesPath = path.join(__dirname, "profiles.json");
+import { setWallet } from "../modules/viem";
 
 export interface Profile {
 	name: string;
@@ -17,7 +18,7 @@ export const getProfiles = () => {
 	return JSON.parse(data) as Profile[];
 };
 
-export const getWallet = (name?: string) => {
+export const getAccount = (name?: string) => {
 	const profiles = getProfiles();
 	const profile = profiles.find((p) => p.name === name) || profiles[0];
 
@@ -26,6 +27,12 @@ export const getWallet = (name?: string) => {
 	return privateKeyToAccount(profile.privateKey);
 };
 
+export const getWallet = (name?: string, chainId?: number | undefined) => {
+	const account = getAccount(name);
+
+	return setWallet(account, chainId);
+};
+
 export const saveProfile = (params: Profile) => {
 	if (!params.privateKey.match(/^0x[0-9a-f]{64}$/)) {
 		console.log("Invalid private key.");
@@ -59,7 +66,7 @@ export const deleteProfile = (params: { name: string }) => {
 
 	writeFileSync(profilesPath, JSON.stringify(profiles, null, 2));
 	console.log(`Profile "${params.name}" with private key has been deleted.`);
-}
+};
 
 export const listProfiles = () => {
 	const profiles = getProfiles();

From 2097527546c7d686cc9a295b291e6e15a5b73a13 Mon Sep 17 00:00:00 2001
From: yawn <69970183+yawn-c111@users.noreply.github.com>
Date: Wed, 2 Oct 2024 22:01:01 +0900
Subject: [PATCH 3/6] feat: Added mintHat command

---
 pkgs/cli/src/abi/hatsTimeFrameModule.ts | 217 ++++++++++++++++++++++++
 pkgs/cli/src/commands/wallet.ts         |  23 ++-
 pkgs/cli/src/modules/hatsProtocol.ts    |  27 +++
 pkgs/cli/src/modules/viem.ts            |  24 ++-
 yarn.lock                               |  31 +---
 5 files changed, 288 insertions(+), 34 deletions(-)
 create mode 100644 pkgs/cli/src/abi/hatsTimeFrameModule.ts

diff --git a/pkgs/cli/src/abi/hatsTimeFrameModule.ts b/pkgs/cli/src/abi/hatsTimeFrameModule.ts
new file mode 100644
index 0000000..4a842b2
--- /dev/null
+++ b/pkgs/cli/src/abi/hatsTimeFrameModule.ts
@@ -0,0 +1,217 @@
+export const HATS_TIME_FRAME_MODULE_ABI = [
+	{
+		inputs: [
+			{
+				internalType: "address",
+				name: "_trustedForwarder",
+				type: "address",
+			},
+			{
+				internalType: "string",
+				name: "_version",
+				type: "string",
+			},
+		],
+		stateMutability: "nonpayable",
+		type: "constructor",
+	},
+	{
+		inputs: [],
+		name: "InvalidInitialization",
+		type: "error",
+	},
+	{
+		inputs: [],
+		name: "NotInitializing",
+		type: "error",
+	},
+	{
+		anonymous: false,
+		inputs: [
+			{
+				indexed: false,
+				internalType: "uint64",
+				name: "version",
+				type: "uint64",
+			},
+		],
+		name: "Initialized",
+		type: "event",
+	},
+	{
+		inputs: [],
+		name: "HATS",
+		outputs: [
+			{
+				internalType: "contract IHats",
+				name: "",
+				type: "address",
+			},
+		],
+		stateMutability: "pure",
+		type: "function",
+	},
+	{
+		inputs: [],
+		name: "IMPLEMENTATION",
+		outputs: [
+			{
+				internalType: "address",
+				name: "",
+				type: "address",
+			},
+		],
+		stateMutability: "pure",
+		type: "function",
+	},
+	{
+		inputs: [
+			{
+				internalType: "address",
+				name: "wearer",
+				type: "address",
+			},
+			{
+				internalType: "uint256",
+				name: "hatId",
+				type: "uint256",
+			},
+		],
+		name: "getWearingElapsedTime",
+		outputs: [
+			{
+				internalType: "uint256",
+				name: "",
+				type: "uint256",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+	{
+		inputs: [
+			{
+				internalType: "address",
+				name: "wearer",
+				type: "address",
+			},
+			{
+				internalType: "uint256",
+				name: "hatId",
+				type: "uint256",
+			},
+		],
+		name: "getWoreTime",
+		outputs: [
+			{
+				internalType: "uint256",
+				name: "",
+				type: "uint256",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+	{
+		inputs: [],
+		name: "hatId",
+		outputs: [
+			{
+				internalType: "uint256",
+				name: "",
+				type: "uint256",
+			},
+		],
+		stateMutability: "pure",
+		type: "function",
+	},
+	{
+		inputs: [
+			{
+				internalType: "address",
+				name: "forwarder",
+				type: "address",
+			},
+		],
+		name: "isTrustedForwarder",
+		outputs: [
+			{
+				internalType: "bool",
+				name: "",
+				type: "bool",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+	{
+		inputs: [
+			{
+				internalType: "uint256",
+				name: "hatId",
+				type: "uint256",
+			},
+			{
+				internalType: "address",
+				name: "wearer",
+				type: "address",
+			},
+		],
+		name: "mintHat",
+		outputs: [],
+		stateMutability: "nonpayable",
+		type: "function",
+	},
+	{
+		inputs: [
+			{
+				internalType: "bytes",
+				name: "_initData",
+				type: "bytes",
+			},
+		],
+		name: "setUp",
+		outputs: [],
+		stateMutability: "nonpayable",
+		type: "function",
+	},
+	{
+		inputs: [],
+		name: "trustedForwarder",
+		outputs: [
+			{
+				internalType: "address",
+				name: "",
+				type: "address",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+	{
+		inputs: [],
+		name: "version",
+		outputs: [
+			{
+				internalType: "string",
+				name: "",
+				type: "string",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+	{
+		inputs: [],
+		name: "version_",
+		outputs: [
+			{
+				internalType: "string",
+				name: "",
+				type: "string",
+			},
+		],
+		stateMutability: "view",
+		type: "function",
+	},
+] as const;
diff --git a/pkgs/cli/src/commands/wallet.ts b/pkgs/cli/src/commands/wallet.ts
index 9bcc2c6..2d113bd 100644
--- a/pkgs/cli/src/commands/wallet.ts
+++ b/pkgs/cli/src/commands/wallet.ts
@@ -1,11 +1,12 @@
 import { Command } from "commander";
-import { getEthAddress, sendEth } from "../modules/viem";
+import { getPublicClient, sendEth } from "../modules/viem";
 import {
 	getWallet,
 	listProfiles,
 	saveProfile,
 	deleteProfile,
 } from "../services/wallet";
+import { mintHat } from "../modules/hatsProtocol";
 
 export const walletCommands = new Command();
 
@@ -57,7 +58,23 @@ walletCommands
 	.requiredOption("--receiver <receiver>", "Receiver address")
 	.requiredOption("--amount <amount>", "Amount")
 	.option("--chainId <chainId>", "chainId")
-	.action(async ({ name, receiver, amount }) => {
-		const wallet = await getWallet(name);
+	.action(async ({ name, receiver, amount, chainId }) => {
+		const wallet = await getWallet(name, chainId);
 		await sendEth(wallet, receiver, amount);
 	});
+
+/**
+ * ロールを付与
+ */
+walletCommands
+	.command("mintHat")
+	.description("Mint Hat")
+	.requiredOption("--name <name>", "Wallet name")
+	.requiredOption("--hatId <hatId>", "Hat ID")
+	.requiredOption("--wearer <wearer>", "Wearer address")
+	.option("--chainId <chainId>", "chainId")
+	.action(async ({ name, hatId, wearer, chainId }) => {
+		const publicClient = await getPublicClient(chainId);
+		const wallet = await getWallet(name, chainId);
+		await mintHat(publicClient, wallet, { hatId, wearer });
+	});
diff --git a/pkgs/cli/src/modules/hatsProtocol.ts b/pkgs/cli/src/modules/hatsProtocol.ts
index d0988e1..c2e2ac3 100644
--- a/pkgs/cli/src/modules/hatsProtocol.ts
+++ b/pkgs/cli/src/modules/hatsProtocol.ts
@@ -1,5 +1,7 @@
 import { HatsSubgraphClient } from "@hatsprotocol/sdk-v1-subgraph";
+import { Address, PublicClient, WalletClient } from "viem";
 import { base, optimism, sepolia } from "viem/chains";
+import { HATS_TIME_FRAME_MODULE_ABI } from "../abi/hatsTimeFrameModule";
 
 // Subgraph用のインスタンスを生成
 export const hatsSubgraphClient = new HatsSubgraphClient({
@@ -92,3 +94,28 @@ export const getWearerInfo = async (walletAddress: string) => {
 
 	return wearer;
 };
+
+const hatsTimeFrameContractBaseConfig = {
+	address: "0x0000000000000000000000000000000000004a75" as Address,
+	abi: HATS_TIME_FRAME_MODULE_ABI,
+};
+
+/**
+ * ロール付与
+ */
+export const mintHat = async (
+	publicClient: PublicClient,
+	walletClient: WalletClient,
+	args: {
+		hatId: bigint;
+		wearer: Address;
+	}
+) => {
+	const { request } = await publicClient.simulateContract({
+		...hatsTimeFrameContractBaseConfig,
+		account: walletClient.account,
+		functionName: "mintHat",
+		args: [args.hatId, args.wearer],
+	});
+	walletClient.writeContract(request);
+};
diff --git a/pkgs/cli/src/modules/viem.ts b/pkgs/cli/src/modules/viem.ts
index 35bab5f..5010da7 100644
--- a/pkgs/cli/src/modules/viem.ts
+++ b/pkgs/cli/src/modules/viem.ts
@@ -1,5 +1,6 @@
 import {
 	Chain,
+	createPublicClient,
 	createWalletClient,
 	http,
 	parseEther,
@@ -11,7 +12,7 @@ import { hardhat, sepolia, holesky } from "viem/chains";
 
 const chains = [hardhat, sepolia, holesky];
 
-function getChainById(chainId: number | string): Chain {
+export const getChainById = (chainId: number | string): Chain => {
 	const numericChainId = Number(chainId);
 
 	const chain = chains.find((c) => c.id === numericChainId);
@@ -21,13 +22,30 @@ function getChainById(chainId: number | string): Chain {
 	}
 
 	return chain;
-}
+};
+
+export const getChainOrDefault = (
+	chainId: number | string | undefined
+): Chain => {
+	return chainId ? getChainById(chainId) : holesky;
+};
+
+export const getPublicClient = async (chainId?: number | undefined) => {
+	const chain = getChainOrDefault(chainId);
+
+	const publicClient = createPublicClient({
+		chain,
+		transport: http(),
+	});
+
+	return publicClient;
+};
 
 export const setWallet = async (
 	account: PrivateKeyAccount,
 	chainId?: number | undefined
 ) => {
-	const chain = chainId ? getChainById(chainId) : holesky;
+	const chain = getChainOrDefault(chainId);
 
 	const wallet = createWalletClient({
 		account,
diff --git a/yarn.lock b/yarn.lock
index 125585a..9262b58 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5330,7 +5330,7 @@ streamsearch@^1.1.0:
   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
   integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
 
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -5347,15 +5347,6 @@ string-width@^2.1.1:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
-string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
 string-width@^5.0.1, string-width@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
@@ -5441,7 +5432,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -5455,13 +5446,6 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -5973,16 +5957,7 @@ workerpool@^6.5.1:
   resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
   integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
-wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

From 99d621106e3d9d20ef4de30cb2496d43285404ac Mon Sep 17 00:00:00 2001
From: yawn <69970183+yawn-c111@users.noreply.github.com>
Date: Wed, 2 Oct 2024 23:11:53 +0900
Subject: [PATCH 4/6] update: configs and chains

---
 pkgs/cli/src/config.ts               | 13 +++++++++++++
 pkgs/cli/src/modules/hatsProtocol.ts | 19 +++++--------------
 pkgs/cli/src/modules/viem.ts         |  4 ++--
 3 files changed, 20 insertions(+), 16 deletions(-)
 create mode 100644 pkgs/cli/src/config.ts

diff --git a/pkgs/cli/src/config.ts b/pkgs/cli/src/config.ts
new file mode 100644
index 0000000..fbbb023
--- /dev/null
+++ b/pkgs/cli/src/config.ts
@@ -0,0 +1,13 @@
+import { Address } from "viem";
+import { HATS_ABI } from "./abi/hats";
+import { HATS_TIME_FRAME_MODULE_ABI } from "./abi/hatsTimeFrameModule";
+
+export const hatsContractBaseConfig = {
+	address: "0x0000000000000000000000000000000000004a75" as Address,
+	abi: HATS_ABI,
+};
+
+export const hatsTimeFrameContractBaseConfig = {
+	address: "0xd4a66507ea8c8382fa8474ed6cae4163676a434a" as Address,
+	abi: HATS_TIME_FRAME_MODULE_ABI,
+};
diff --git a/pkgs/cli/src/modules/hatsProtocol.ts b/pkgs/cli/src/modules/hatsProtocol.ts
index bfe67d4..1bf7591 100644
--- a/pkgs/cli/src/modules/hatsProtocol.ts
+++ b/pkgs/cli/src/modules/hatsProtocol.ts
@@ -1,9 +1,10 @@
 import { HatsSubgraphClient } from "@hatsprotocol/sdk-v1-subgraph";
-import { Address, getContract, PublicClient, WalletClient } from "viem";
+import { Address, PublicClient, WalletClient } from "viem";
 import { base, optimism, sepolia } from "viem/chains";
-import { HATS_ABI } from "../abi/hats";
-import { HATS_TIME_FRAME_MODULE_ABI } from "../abi/hatsTimeFrameModule";
-import { simulateContract } from "viem/_types/actions/public/simulateContract";
+import {
+	hatsContractBaseConfig,
+	hatsTimeFrameContractBaseConfig,
+} from "../config";
 
 // ###############################################################
 // Read with subgraph
@@ -105,16 +106,6 @@ export const getWearerInfo = async (walletAddress: string) => {
 // Write with viem
 // ###############################################################
 
-const hatsContractBaseConfig = {
-	address: "0x0000000000000000000000000000000000004a75" as Address,
-	abi: HATS_ABI,
-};
-
-const hatsTimeFrameContractBaseConfig = {
-	address: "0x0000000000000000000000000000000000004a75" as Address,
-	abi: HATS_TIME_FRAME_MODULE_ABI,
-};
-
 /**
  * 新規Hat作成
  */
diff --git a/pkgs/cli/src/modules/viem.ts b/pkgs/cli/src/modules/viem.ts
index 5010da7..267747b 100644
--- a/pkgs/cli/src/modules/viem.ts
+++ b/pkgs/cli/src/modules/viem.ts
@@ -10,7 +10,7 @@ import {
 import { privateKeyToAccount } from "viem/accounts";
 import { hardhat, sepolia, holesky } from "viem/chains";
 
-const chains = [hardhat, sepolia, holesky];
+const chains = [hardhat, holesky, sepolia];
 
 export const getChainById = (chainId: number | string): Chain => {
 	const numericChainId = Number(chainId);
@@ -27,7 +27,7 @@ export const getChainById = (chainId: number | string): Chain => {
 export const getChainOrDefault = (
 	chainId: number | string | undefined
 ): Chain => {
-	return chainId ? getChainById(chainId) : holesky;
+	return chainId ? getChainById(chainId) : sepolia;
 };
 
 export const getPublicClient = async (chainId?: number | undefined) => {

From 9fe6f7cadd3f6df303bd978c0e16ff56f6e6be04 Mon Sep 17 00:00:00 2001
From: yawn <69970183+yawn-c111@users.noreply.github.com>
Date: Wed, 2 Oct 2024 23:24:52 +0900
Subject: [PATCH 5/6] update: Hats contract address for sepolia

---
 pkgs/cli/src/config.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pkgs/cli/src/config.ts b/pkgs/cli/src/config.ts
index fbbb023..9594678 100644
--- a/pkgs/cli/src/config.ts
+++ b/pkgs/cli/src/config.ts
@@ -3,7 +3,7 @@ import { HATS_ABI } from "./abi/hats";
 import { HATS_TIME_FRAME_MODULE_ABI } from "./abi/hatsTimeFrameModule";
 
 export const hatsContractBaseConfig = {
-	address: "0x0000000000000000000000000000000000004a75" as Address,
+	address: "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137" as Address,
 	abi: HATS_ABI,
 };
 

From 83497c618a32699844b6fff95b82b38501692e22 Mon Sep 17 00:00:00 2001
From: yu23ki14 <yuki_021423@yahoo.co.jp>
Date: Thu, 3 Oct 2024 20:07:19 +0900
Subject: [PATCH 6/6] fix command

---
 pkgs/cli/src/commands/function.ts    | 70 ----------------------------
 pkgs/cli/src/commands/hats.ts        | 23 +++++++--
 pkgs/cli/src/commands/wallet.ts      | 34 ++------------
 pkgs/cli/src/config.ts               |  2 +
 pkgs/cli/src/index.ts                | 35 +++++++++++---
 pkgs/cli/src/modules/hatsProtocol.ts | 22 ++++-----
 pkgs/cli/src/services/wallet.ts      |  8 +++-
 7 files changed, 70 insertions(+), 124 deletions(-)
 delete mode 100644 pkgs/cli/src/commands/function.ts

diff --git a/pkgs/cli/src/commands/function.ts b/pkgs/cli/src/commands/function.ts
deleted file mode 100644
index 5fda2d9..0000000
--- a/pkgs/cli/src/commands/function.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Command } from "commander";
-
-export const functionCommands = new Command();
-
-// ###############################################################
-// CLI init setup
-// ###############################################################
-
-functionCommands
-	.name("function")
-	.description("This is a CLI function for toban project")
-	.version("1.0.0");
-
-// ###############################################################
-// command setUp
-// ###############################################################
-
-// シフトデータを保持するための簡易データ
-let shifts = [
-	{ date: "2024-10-01", person: "Alice" },
-	{ date: "2024-10-02", person: "Bob" },
-];
-
-/**
- * シフトを一覧表示するコマンド
- */
-functionCommands
-	.command("list")
-	.description("List all shifts")
-	.action(() => {
-		console.log("Current shifts:");
-		shifts.forEach((shift, index) => {
-			console.log(`${index + 1}. ${shift.date} - ${shift.person}`);
-		});
-	});
-
-/**
- * 新しいシフトを追加するコマンド
- */
-functionCommands
-	.command("add <date> <person>")
-	.description("Add a new shift")
-	.action((date, person) => {
-		shifts.push({ date, person });
-		console.log(`Added new shift: ${date} - ${person}`);
-	});
-
-/**
- * ランダムで担当者を選ぶコマンド
- */
-functionCommands
-	.command("random")
-	.description("Pick a random person for the shift")
-	.action(() => {
-		const randomIndex = Math.floor(Math.random() * shifts.length);
-		console.log(
-			`Selected: ${shifts[randomIndex].person} for ${shifts[randomIndex].date}`
-		);
-	});
-
-/**
- * 引数を表示するだけのコマンド
- */
-functionCommands
-	.command("show")
-	.description("Show the arguments")
-	.option("-t, --text <text>", "Show all arguments")
-	.action(async (options) => {
-		console.log("your args:", options.text);
-	});
diff --git a/pkgs/cli/src/commands/hats.ts b/pkgs/cli/src/commands/hats.ts
index 46b3c41..3a46acd 100644
--- a/pkgs/cli/src/commands/hats.ts
+++ b/pkgs/cli/src/commands/hats.ts
@@ -3,7 +3,10 @@ import {
 	getTreeInfo,
 	getWearerInfo,
 	getWearersInfo,
+	mintHat,
 } from "../modules/hatsProtocol";
+import { getAccount } from "../services/wallet";
+import { publicClient, rootProgram, walletClient } from "..";
 
 export const hatsCommands = new Command();
 
@@ -29,7 +32,7 @@ hatsCommands
 	.option("-id, --treeId <treeId>", "Tree ID")
 	.action(async (options) => {
 		// ツリー情報を全て取得する。
-		const tree = await getTreeInfo(Number(options.treeId));
+		const tree = await getTreeInfo(Number(options.treeId), options.chainId);
 
 		console.log(tree);
 	});
@@ -43,7 +46,7 @@ hatsCommands
 	.option("-id, --hatId <hatId>", "Hat ID")
 	.action(async (options) => {
 		// ツリー情報を全て取得する。
-		const wearers = await getWearersInfo(options.hatId);
+		const wearers = await getWearersInfo(options.hatId, options.chainId);
 
 		console.log(wearers);
 	});
@@ -57,7 +60,21 @@ hatsCommands
 	.option("-addr, --address <address>", "Wallet Address")
 	.action(async (options) => {
 		// 特定のウォレットアドレスに紐づく情報を全て取得する。
-		const wearer = await getWearerInfo(options.address);
+		const address =
+			options.address || getAccount(rootProgram.opts().profile).address;
+		const wearer = await getWearerInfo(address, rootProgram.opts().chain);
 
 		console.log(wearer);
 	});
+
+/**
+ * ロールを付与
+ */
+hatsCommands
+	.command("mintHat")
+	.description("Mint Hat")
+	.requiredOption("--hatId <hatId>", "Hat ID")
+	.requiredOption("--wearer <wearer>", "Wearer address")
+	.action(async ({ hatId, wearer }) => {
+		await mintHat({ hatId, wearer });
+	});
diff --git a/pkgs/cli/src/commands/wallet.ts b/pkgs/cli/src/commands/wallet.ts
index 2d113bd..a087141 100644
--- a/pkgs/cli/src/commands/wallet.ts
+++ b/pkgs/cli/src/commands/wallet.ts
@@ -1,12 +1,7 @@
 import { Command } from "commander";
-import { getPublicClient, sendEth } from "../modules/viem";
-import {
-	getWallet,
-	listProfiles,
-	saveProfile,
-	deleteProfile,
-} from "../services/wallet";
-import { mintHat } from "../modules/hatsProtocol";
+import { sendEth } from "../modules/viem";
+import { listProfiles, saveProfile, deleteProfile } from "../services/wallet";
+import { walletClient } from "..";
 
 export const walletCommands = new Command();
 
@@ -54,27 +49,8 @@ walletCommands
 walletCommands
 	.command("sendEth")
 	.description("Send ETH")
-	.requiredOption("--name <name>", "Wallet name")
 	.requiredOption("--receiver <receiver>", "Receiver address")
 	.requiredOption("--amount <amount>", "Amount")
-	.option("--chainId <chainId>", "chainId")
-	.action(async ({ name, receiver, amount, chainId }) => {
-		const wallet = await getWallet(name, chainId);
-		await sendEth(wallet, receiver, amount);
-	});
-
-/**
- * ロールを付与
- */
-walletCommands
-	.command("mintHat")
-	.description("Mint Hat")
-	.requiredOption("--name <name>", "Wallet name")
-	.requiredOption("--hatId <hatId>", "Hat ID")
-	.requiredOption("--wearer <wearer>", "Wearer address")
-	.option("--chainId <chainId>", "chainId")
-	.action(async ({ name, hatId, wearer, chainId }) => {
-		const publicClient = await getPublicClient(chainId);
-		const wallet = await getWallet(name, chainId);
-		await mintHat(publicClient, wallet, { hatId, wearer });
+	.action(async ({ receiver, amount }) => {
+		await sendEth(walletClient, receiver, amount);
 	});
diff --git a/pkgs/cli/src/config.ts b/pkgs/cli/src/config.ts
index 9594678..4bd8d43 100644
--- a/pkgs/cli/src/config.ts
+++ b/pkgs/cli/src/config.ts
@@ -2,6 +2,8 @@ import { Address } from "viem";
 import { HATS_ABI } from "./abi/hats";
 import { HATS_TIME_FRAME_MODULE_ABI } from "./abi/hatsTimeFrameModule";
 
+export const skipPreActionCommands = ["wallet>add", "wallet>list"];
+
 export const hatsContractBaseConfig = {
 	address: "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137" as Address,
 	abi: HATS_ABI,
diff --git a/pkgs/cli/src/index.ts b/pkgs/cli/src/index.ts
index 34e3516..c3a1ee8 100755
--- a/pkgs/cli/src/index.ts
+++ b/pkgs/cli/src/index.ts
@@ -1,12 +1,35 @@
 #!/usr/bin/env node
 
-import { program } from "commander";
-import { functionCommands } from "./commands/function";
+import { Command } from "commander";
 import { hatsCommands } from "./commands/hats";
 import { walletCommands } from "./commands/wallet";
+import { PublicClient, WalletClient } from "viem";
+import { getPublicClient } from "./modules/viem";
+import { getWalletClient } from "./services/wallet";
+import { skipPreActionCommands } from "./config";
 
-program.addCommand(functionCommands);
-program.addCommand(hatsCommands);
-program.addCommand(walletCommands);
+export const rootProgram = new Command();
 
-program.parse(process.argv);
+export let publicClient!: PublicClient;
+
+export let walletClient!: WalletClient;
+
+rootProgram
+	.version("0.0.1")
+	.option("--chain <chain>", "chain id", "11155111")
+	.option("--profile <profile>", "Wallet profile")
+	.hook("preAction", async (_, actionCommand) => {
+		const { chain, profile } = rootProgram.opts();
+		const parentName = actionCommand.parent?.name();
+		const commandName = actionCommand.name();
+
+		if (!skipPreActionCommands.includes(`${parentName}>${commandName}`)) {
+			publicClient = await getPublicClient(chain);
+			walletClient = await getWalletClient(profile, chain);
+		}
+	});
+
+rootProgram.addCommand(hatsCommands);
+rootProgram.addCommand(walletCommands);
+
+rootProgram.parse(process.argv);
diff --git a/pkgs/cli/src/modules/hatsProtocol.ts b/pkgs/cli/src/modules/hatsProtocol.ts
index 1bf7591..25a1d0c 100644
--- a/pkgs/cli/src/modules/hatsProtocol.ts
+++ b/pkgs/cli/src/modules/hatsProtocol.ts
@@ -5,6 +5,7 @@ import {
 	hatsContractBaseConfig,
 	hatsTimeFrameContractBaseConfig,
 } from "../config";
+import { publicClient, walletClient } from "..";
 
 // ###############################################################
 // Read with subgraph
@@ -31,9 +32,9 @@ export const hatsSubgraphClient = new HatsSubgraphClient({
 /**
  * ツリー情報を取得するメソッド
  */
-export const getTreeInfo = async (treeId: number) => {
+export const getTreeInfo = async (treeId: number, chainId: number) => {
 	const tree = await hatsSubgraphClient.getTree({
-		chainId: optimism.id,
+		chainId,
 		treeId: treeId,
 		props: {
 			hats: {
@@ -59,10 +60,10 @@ export const getTreeInfo = async (treeId: number) => {
 /**
  * 帽子の着用者のウォレットアドレスを一覧を取得するメソッド
  */
-export const getWearersInfo = async (hatId: string) => {
+export const getWearersInfo = async (hatId: string, chainId: number) => {
 	// get the first 10 wearers of a given hat
 	const wearers = await hatsSubgraphClient.getWearersOfHatPaginated({
-		chainId: optimism.id,
+		chainId,
 		props: {},
 		hatId: BigInt(hatId),
 		page: 0,
@@ -75,10 +76,10 @@ export const getWearersInfo = async (hatId: string) => {
 /**
  * 特定のウォレットアドレスが着用している全てのHats情報を取得するメソッド
  */
-export const getWearerInfo = async (walletAddress: string) => {
+export const getWearerInfo = async (walletAddress: string, chainId: number) => {
 	// get the wearer of a given hat
 	const wearer = await hatsSubgraphClient.getWearer({
-		chainId: optimism.id,
+		chainId,
 		wearerAddress: walletAddress as `0x${string}`,
 		props: {
 			currentHats: {
@@ -142,14 +143,7 @@ export const createHat = async (
 /**
  * ロール付与
  */
-export const mintHat = async (
-	publicClient: PublicClient,
-	walletClient: WalletClient,
-	args: {
-		hatId: bigint;
-		wearer: Address;
-	}
-) => {
+export const mintHat = async (args: { hatId: bigint; wearer: Address }) => {
 	const { request } = await publicClient.simulateContract({
 		...hatsTimeFrameContractBaseConfig,
 		account: walletClient.account,
diff --git a/pkgs/cli/src/services/wallet.ts b/pkgs/cli/src/services/wallet.ts
index b96e838..6c4d388 100644
--- a/pkgs/cli/src/services/wallet.ts
+++ b/pkgs/cli/src/services/wallet.ts
@@ -22,12 +22,16 @@ export const getAccount = (name?: string) => {
 	const profiles = getProfiles();
 	const profile = profiles.find((p) => p.name === name) || profiles[0];
 
-	if (!profile) throw "Profile not found.";
+	if (!profile)
+		throw "Profile not found. Please add a profile with wallet add command.";
 
 	return privateKeyToAccount(profile.privateKey);
 };
 
-export const getWallet = (name?: string, chainId?: number | undefined) => {
+export const getWalletClient = (
+	name?: string,
+	chainId?: number | undefined
+) => {
 	const account = getAccount(name);
 
 	return setWallet(account, chainId);