diff --git a/pkgs/cli/src/abi/fractiontoken.ts b/pkgs/cli/src/abi/fractiontoken.ts new file mode 100644 index 0000000..a88416f --- /dev/null +++ b/pkgs/cli/src/abi/fractiontoken.ts @@ -0,0 +1,634 @@ +export const FRACTION_TOKEN_ABI = [ + { + inputs: [ + { + internalType: "string", + name: "_uri", + type: "string", + }, + { + internalType: "uint256", + name: "_tokenSupply", + type: "uint256", + }, + { + internalType: "address", + name: "_hatsAddress", + type: "address", + }, + { + internalType: "address", + name: "_trustedForwarderAddress", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + { + internalType: "uint256", + name: "needed", + type: "uint256", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "ERC1155InsufficientBalance", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "approver", + type: "address", + }, + ], + name: "ERC1155InvalidApprover", + type: "error", + }, + { + inputs: [ + { + internalType: "uint256", + name: "idsLength", + type: "uint256", + }, + { + internalType: "uint256", + name: "valuesLength", + type: "uint256", + }, + ], + name: "ERC1155InvalidArrayLength", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address", + }, + ], + name: "ERC1155InvalidOperator", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "receiver", + type: "address", + }, + ], + name: "ERC1155InvalidReceiver", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "ERC1155InvalidSender", + type: "error", + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address", + }, + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "ERC1155MissingApprovalForAll", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + { + indexed: false, + internalType: "uint256[]", + name: "values", + type: "uint256[]", + }, + ], + name: "TransferBatch", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "id", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "TransferSingle", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "string", + name: "value", + type: "string", + }, + { + indexed: true, + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "URI", + type: "event", + }, + { + inputs: [], + name: "TOKEN_SUPPLY", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "uint256", + name: "id", + type: "uint256", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "address", + name: "wearer", + type: "address", + }, + { + internalType: "uint256", + name: "hatId", + type: "uint256", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address[]", + name: "accounts", + type: "address[]", + }, + { + internalType: "address[]", + name: "wearers", + type: "address[]", + }, + { + internalType: "uint256[]", + name: "hatIds", + type: "uint256[]", + }, + ], + name: "balanceOfBatch", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address[]", + name: "accounts", + type: "address[]", + }, + { + internalType: "uint256[]", + name: "ids", + type: "uint256[]", + }, + ], + name: "balanceOfBatch", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "wearer", + type: "address", + }, + { + internalType: "uint256", + name: "hatId", + type: "uint256", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "burn", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "hatId", + type: "uint256", + }, + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "getTokenId", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "getTokenRecipients", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + { + internalType: "address", + name: "operator", + type: "address", + }, + ], + name: "isApprovedForAll", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + 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: "account", + type: "address", + }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256[]", + name: "tokenIds", + type: "uint256[]", + }, + { + internalType: "uint256[]", + name: "amounts", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "safeBatchTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "from", + type: "address", + }, + { + internalType: "address", + name: "to", + type: "address", + }, + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "safeTransferFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "operator", + type: "address", + }, + { + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "setApprovalForAll", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "interfaceId", + type: "bytes4", + }, + ], + name: "supportsInterface", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "trustedForwarder", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "tokenId", + type: "uint256", + }, + ], + name: "uri", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/pkgs/cli/src/commands/fractionToken.ts b/pkgs/cli/src/commands/fractionToken.ts new file mode 100644 index 0000000..9561b64 --- /dev/null +++ b/pkgs/cli/src/commands/fractionToken.ts @@ -0,0 +1,39 @@ +import { Command } from "commander"; +import { mintFractionToken, sendFractionToken } from "../modules/fractiontoken"; + +export const fractionTokenCommands = new Command(); + +fractionTokenCommands + .name("fractionToken") + .description("This is a CLI fractionToken for toban project") + .version("1.0.0"); + +fractionTokenCommands + .command("mint") + .description("Mint fraction token") + .requiredOption("-h, --hatId ", "Hat ID") + .requiredOption("-a, --account ", "Account") + .action(async (options) => { + const transactionHash = await mintFractionToken( + BigInt(options.hatId), + options.account + ); + + console.log(`Transaction hash: ${transactionHash}`); + }); + +fractionTokenCommands + .command("send") + .description("Send fraction token") + .requiredOption("-t, --to ", "To") + .requiredOption("-h, --hatId ", "Hat ID") + .requiredOption("-a, --amount ", "Amount") + .action(async (options) => { + const transactionHash = await sendFractionToken( + options.to, + BigInt(options.hatId), + BigInt(options.amount) + ); + + console.log(`Transaction hash: ${transactionHash}`); + }); diff --git a/pkgs/cli/src/config.ts b/pkgs/cli/src/config.ts index 0dbbe57..de9e121 100644 --- a/pkgs/cli/src/config.ts +++ b/pkgs/cli/src/config.ts @@ -2,6 +2,7 @@ import { Address } from "viem"; import { HATS_ABI } from "./abi/hats"; import { HATS_TIME_FRAME_MODULE_ABI } from "./abi/hatsTimeFrameModule"; import { BIGBANG_ABI } from "./abi/bigbang"; +import { FRACTION_TOKEN_ABI } from "./abi/fractiontoken"; export const skipPreActionCommands = ["wallet>add", "wallet>list"]; @@ -18,3 +19,8 @@ export const bigbangContractBaseConfig = { address: "0x5d7a64Cc808294C516076d371685ed4E6aDd6337" as Address, abi: BIGBANG_ABI, }; + +export const fractionTokenBaseConfig = { + address: "0xb8f7ca7a5b1e457b8735884419e114f90d53e1d5" as Address, + abi: FRACTION_TOKEN_ABI, +}; diff --git a/pkgs/cli/src/index.ts b/pkgs/cli/src/index.ts index a75ea09..034fdd3 100755 --- a/pkgs/cli/src/index.ts +++ b/pkgs/cli/src/index.ts @@ -9,6 +9,7 @@ import { getWalletClient } from "./services/wallet"; import { skipPreActionCommands } from "./config"; import { bigbangCommands } from "./commands/bigbang"; import { pinataCommands } from "./commands/pinata"; +import { fractionTokenCommands } from "./commands/fractionToken"; export const rootProgram = new Command(); @@ -35,5 +36,6 @@ rootProgram.addCommand(bigbangCommands); rootProgram.addCommand(hatsCommands); rootProgram.addCommand(walletCommands); rootProgram.addCommand(pinataCommands); +rootProgram.addCommand(fractionTokenCommands); rootProgram.parse(process.argv); diff --git a/pkgs/cli/src/modules/fractiontoken.ts b/pkgs/cli/src/modules/fractiontoken.ts new file mode 100644 index 0000000..7b0ff6a --- /dev/null +++ b/pkgs/cli/src/modules/fractiontoken.ts @@ -0,0 +1,62 @@ +import { Address, decodeEventLog } from "viem"; +import { publicClient, walletClient } from ".."; +import { fractionTokenBaseConfig } from "../config"; +import { startLoading } from "../services/loading"; + +export const getTokenId = async (hatId: bigint, account: Address) => { + const res = await publicClient.readContract({ + ...fractionTokenBaseConfig, + functionName: "getTokenId", + args: [hatId, account], + }); + + return res; +}; + +export const mintFractionToken = async (hatId: bigint, account: Address) => { + const { request } = await publicClient.simulateContract({ + ...fractionTokenBaseConfig, + account: walletClient.account, + functionName: "mint", + args: [hatId, account], + }); + const transactionHash = await walletClient.writeContract(request); + + return transactionHash; +}; + +export const sendFractionToken = async ( + to: Address, + hatId: bigint, + amount: bigint +) => { + const stop = startLoading(); + const tokenId = await getTokenId(hatId, walletClient.account?.address!); + + const { request } = await publicClient.simulateContract({ + ...fractionTokenBaseConfig, + account: walletClient.account, + functionName: "safeTransferFrom", + args: [walletClient.account?.address!, to, tokenId, amount, "" as any], + }); + const transactionHash = await walletClient.writeContract(request); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: transactionHash, + }); + + const log = receipt.logs.find((log) => { + const decodedLog = decodeEventLog({ + abi: fractionTokenBaseConfig.abi, + data: log.data, + topics: log.topics, + }); + return decodedLog.eventName === "TransferSingle"; + }); + + stop(); + + console.log(log); + + return transactionHash; +}; diff --git a/pkgs/cli/src/services/loading.ts b/pkgs/cli/src/services/loading.ts new file mode 100644 index 0000000..c782f5a --- /dev/null +++ b/pkgs/cli/src/services/loading.ts @@ -0,0 +1,14 @@ +export const startLoading = () => { + const brailleChars = ["⠁", "⠃", "⠇", "⡇", "⡏", "⡟", "⡿", "⡿", "⣿"]; + + let index = 0; + const interval = setInterval(() => { + process.stdout.write(`\rLoading ${brailleChars[index]} `); + index = (index + 1) % brailleChars.length; + }, 200); + + return () => { + clearInterval(interval); + console.log("Done!\n"); + }; +};